Convert 10 PEPs to reSt (#180)
This commit is contained in:
parent
fdc405df22
commit
87dc92a34e
195
pep-0212.txt
195
pep-0212.txt
|
@ -5,163 +5,180 @@ Last-Modified: $Date$
|
|||
Author: nowonder@nowonder.de (Peter Schneider-Kamp)
|
||||
Status: Deferred
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 22-Aug-2000
|
||||
Python-Version: 2.1
|
||||
Post-History:
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This PEP describes the often proposed feature of exposing the loop
|
||||
counter in for-loops. This PEP tracks the status and ownership of
|
||||
this feature. It contains a description of the feature and
|
||||
outlines changes necessary to support the feature. This PEP
|
||||
summarizes discussions held in mailing list forums, and provides
|
||||
URLs for further information, where appropriate. The CVS revision
|
||||
history of this file contains the definitive historical record.
|
||||
This PEP describes the often proposed feature of exposing the loop
|
||||
counter in for-loops. This PEP tracks the status and ownership of
|
||||
this feature. It contains a description of the feature and
|
||||
outlines changes necessary to support the feature. This PEP
|
||||
summarizes discussions held in mailing list forums, and provides
|
||||
URLs for further information, where appropriate. The CVS revision
|
||||
history of this file contains the definitive historical record.
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
Standard for-loops in Python iterate over the elements of a
|
||||
sequence[1]. Often it is desirable to loop over the indices or
|
||||
both the elements and the indices instead.
|
||||
Standard for-loops in Python iterate over the elements of a
|
||||
sequence [1]_. Often it is desirable to loop over the indices or
|
||||
both the elements and the indices instead.
|
||||
|
||||
The common idioms used to accomplish this are unintuitive. This
|
||||
PEP proposes two different ways of exposing the indices.
|
||||
The common idioms used to accomplish this are unintuitive. This
|
||||
PEP proposes two different ways of exposing the indices.
|
||||
|
||||
|
||||
Loop counter iteration
|
||||
======================
|
||||
|
||||
The current idiom for looping over the indices makes use of the
|
||||
built-in 'range' function:
|
||||
The current idiom for looping over the indices makes use of the
|
||||
built-in ``range`` function::
|
||||
|
||||
for i in range(len(sequence)):
|
||||
# work with index i
|
||||
for i in range(len(sequence)):
|
||||
# work with index i
|
||||
|
||||
Looping over both elements and indices can be achieved either by the
|
||||
old idiom or by using the new 'zip' built-in function[2]:
|
||||
Looping over both elements and indices can be achieved either by the
|
||||
old idiom or by using the new ``zip`` built-in function [2]_::
|
||||
|
||||
for i in range(len(sequence)):
|
||||
e = sequence[i]
|
||||
# work with index i and element e
|
||||
for i in range(len(sequence)):
|
||||
e = sequence[i]
|
||||
# work with index i and element e
|
||||
|
||||
or
|
||||
or::
|
||||
|
||||
for i, e in zip(range(len(sequence)), sequence):
|
||||
# work with index i and element e
|
||||
for i, e in zip(range(len(sequence)), sequence):
|
||||
# work with index i and element e
|
||||
|
||||
|
||||
The Proposed Solutions
|
||||
======================
|
||||
|
||||
There are three solutions that have been discussed. One adds a
|
||||
non-reserved keyword, the other adds two built-in functions.
|
||||
A third solution adds methods to sequence objects.
|
||||
There are three solutions that have been discussed. One adds a
|
||||
non-reserved keyword, the other adds two built-in functions.
|
||||
A third solution adds methods to sequence objects.
|
||||
|
||||
|
||||
Non-reserved keyword 'indexing'
|
||||
Non-reserved keyword ``indexing``
|
||||
=================================
|
||||
|
||||
This solution would extend the syntax of the for-loop by adding
|
||||
an optional '<variable> indexing' clause which can also be used
|
||||
instead of the '<variable> in' clause..
|
||||
This solution would extend the syntax of the for-loop by adding
|
||||
an optional ``<variable> indexing`` clause which can also be used
|
||||
instead of the ``<variable> in`` clause.
|
||||
|
||||
Looping over the indices of a sequence would thus become:
|
||||
Looping over the indices of a sequence would thus become::
|
||||
|
||||
for i indexing sequence:
|
||||
# work with index i
|
||||
for i indexing sequence:
|
||||
# work with index i
|
||||
|
||||
Looping over both indices and elements would similarly be:
|
||||
Looping over both indices and elements would similarly be::
|
||||
|
||||
for i indexing e in sequence:
|
||||
# work with index i and element e
|
||||
for i indexing e in sequence:
|
||||
# work with index i and element e
|
||||
|
||||
|
||||
Built-in functions 'indices' and 'irange'
|
||||
Built-in functions ``indices`` and ``irange``
|
||||
=============================================
|
||||
|
||||
This solution adds two built-in functions 'indices' and 'irange'.
|
||||
The semantics of these can be described as follows:
|
||||
This solution adds two built-in functions ``indices`` and ``irange``.
|
||||
The semantics of these can be described as follows::
|
||||
|
||||
def indices(sequence):
|
||||
return range(len(sequence))
|
||||
def indices(sequence):
|
||||
return range(len(sequence))
|
||||
|
||||
def irange(sequence):
|
||||
return zip(range(len(sequence)), sequence)
|
||||
def irange(sequence):
|
||||
return zip(range(len(sequence)), sequence)
|
||||
|
||||
These functions could be implemented either eagerly or lazily and
|
||||
should be easy to extend in order to accept more than one sequence
|
||||
argument.
|
||||
These functions could be implemented either eagerly or lazily and
|
||||
should be easy to extend in order to accept more than one sequence
|
||||
argument.
|
||||
|
||||
The use of these functions would simplify the idioms for looping
|
||||
over the indices and over both elements and indices:
|
||||
The use of these functions would simplify the idioms for looping
|
||||
over the indices and over both elements and indices::
|
||||
|
||||
for i in indices(sequence):
|
||||
# work with index i
|
||||
for i in indices(sequence):
|
||||
# work with index i
|
||||
|
||||
for i, e in irange(sequence):
|
||||
# work with index i and element e
|
||||
for i, e in irange(sequence):
|
||||
# work with index i and element e
|
||||
|
||||
|
||||
Methods for sequence objects
|
||||
============================
|
||||
|
||||
This solution proposes the addition of 'indices', 'items'
|
||||
and 'values' methods to sequences, which enable looping over
|
||||
indices only, both indices and elements, and elements only
|
||||
respectively.
|
||||
This solution proposes the addition of ``indices``, ``items``
|
||||
and ``values`` methods to sequences, which enable looping over
|
||||
indices only, both indices and elements, and elements only
|
||||
respectively.
|
||||
|
||||
This would immensely simplify the idioms for looping over indices
|
||||
and for looping over both elements and indices:
|
||||
This would immensely simplify the idioms for looping over indices
|
||||
and for looping over both elements and indices::
|
||||
|
||||
for i in sequence.indices():
|
||||
# work with index i
|
||||
for i in sequence.indices():
|
||||
# work with index i
|
||||
|
||||
for i, e in sequence.items():
|
||||
# work with index i and element e
|
||||
for i, e in sequence.items():
|
||||
# work with index i and element e
|
||||
|
||||
Additionally it would allow to do looping over the elements
|
||||
of sequences and dicitionaries in a consistent way:
|
||||
Additionally it would allow to do looping over the elements
|
||||
of sequences and dictionaries in a consistent way::
|
||||
|
||||
for e in sequence_or_dict.values():
|
||||
# do something with element e
|
||||
for e in sequence_or_dict.values():
|
||||
# do something with element e
|
||||
|
||||
|
||||
Implementations
|
||||
===============
|
||||
|
||||
For all three solutions some more or less rough patches exist
|
||||
as patches at SourceForge:
|
||||
For all three solutions some more or less rough patches exist
|
||||
as patches at SourceForge:
|
||||
|
||||
'for i indexing a in l': exposing the for-loop counter[3]
|
||||
add indices() and irange() to built-ins[4]
|
||||
add items() method to listobject[5]
|
||||
- ``for i indexing a in l``: exposing the for-loop counter [3]_
|
||||
- add ``indices()`` and ``irange()`` to built-ins [4]_
|
||||
- add ``items()`` method to listobject [5]_
|
||||
|
||||
All of them have been pronounced on and rejected by the BDFL.
|
||||
All of them have been pronounced on and rejected by the BDFL.
|
||||
|
||||
Note that the 'indexing' keyword is only a NAME in the
|
||||
grammar and so does not hinder the general use of 'indexing'.
|
||||
Note that the ``indexing`` keyword is only a ``NAME`` in the
|
||||
grammar and so does not hinder the general use of ``indexing``.
|
||||
|
||||
|
||||
Backward Compatibility Issues
|
||||
=============================
|
||||
|
||||
As no keywords are added and the semantics of existing code
|
||||
remains unchanged, all three solutions can be implemented
|
||||
without breaking existing code.
|
||||
As no keywords are added and the semantics of existing code
|
||||
remains unchanged, all three solutions can be implemented
|
||||
without breaking existing code.
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] http://docs.python.org/reference/compound_stmts.html#for
|
||||
[2] Lockstep Iteration, PEP 201
|
||||
[3] http://sourceforge.net/patch/download.php?id=101138
|
||||
[4] http://sourceforge.net/patch/download.php?id=101129
|
||||
[5] http://sourceforge.net/patch/download.php?id=101178
|
||||
.. [1] http://docs.python.org/reference/compound_stmts.html#for
|
||||
|
||||
.. [2] Lockstep Iteration, PEP 201
|
||||
|
||||
.. [3] http://sourceforge.net/patch/download.php?id=101138
|
||||
|
||||
.. [4] http://sourceforge.net/patch/download.php?id=101129
|
||||
|
||||
.. [5] http://sourceforge.net/patch/download.php?id=101178
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
|
201
pep-0216.txt
201
pep-0216.txt
|
@ -5,139 +5,164 @@ Last-Modified: $Date$
|
|||
Author: moshez@zadka.site.co.il (Moshe Zadka)
|
||||
Status: Rejected
|
||||
Type: Informational
|
||||
Content-Type: text/x-rst
|
||||
Created: 31-Jul-2000
|
||||
Post-History:
|
||||
Superseded-By: 287
|
||||
|
||||
Notice
|
||||
|
||||
This PEP is rejected by the author. It has been superseded by PEP
|
||||
287.
|
||||
Notice
|
||||
======
|
||||
|
||||
This PEP is rejected by the author. It has been superseded by PEP
|
||||
287.
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
Named Python objects, such as modules, classes and functions, have a
|
||||
string attribute called __doc__. If the first expression inside
|
||||
the definition is a literal string, that string is assigned
|
||||
to the __doc__ attribute.
|
||||
Named Python objects, such as modules, classes and functions, have a
|
||||
string attribute called ``__doc__``. If the first expression inside
|
||||
the definition is a literal string, that string is assigned
|
||||
to the ``__doc__`` attribute.
|
||||
|
||||
The ``__doc__`` attribute is called a documentation string, or docstring.
|
||||
It is often used to summarize the interface of the module, class or
|
||||
function. However, since there is no common format for documentation
|
||||
string, tools for extracting docstrings and transforming those into
|
||||
documentation in a standard format (e.g., DocBook) have not sprang
|
||||
up in abundance, and those that do exist are for the most part
|
||||
unmaintained and unused.
|
||||
|
||||
The __doc__ attribute is called a documentation string, or docstring.
|
||||
It is often used to summarize the interface of the module, class or
|
||||
function. However, since there is no common format for documentation
|
||||
string, tools for extracting docstrings and transforming those into
|
||||
documentation in a standard format (e.g., DocBook) have not sprang
|
||||
up in abundance, and those that do exist are for the most part
|
||||
unmaintained and unused.
|
||||
|
||||
Perl Documentation
|
||||
==================
|
||||
|
||||
In Perl, most modules are documented in a format called POD -- Plain
|
||||
Old Documentation. This is an easy-to-type, very low level format
|
||||
which integrates well with the Perl parser. Many tools exist to turn
|
||||
POD documentation into other formats: info, HTML and man pages, among
|
||||
others. However, in Perl, the information is not available at run-time.
|
||||
|
||||
In Perl, most modules are documented in a format called POD -- Plain
|
||||
Old Documentation. This is an easy-to-type, very low level format
|
||||
which integrates well with the Perl parser. Many tools exist to turn
|
||||
POD documentation into other formats: info, HTML and man pages, among
|
||||
others. However, in Perl, the information is not available at run-time.
|
||||
|
||||
Java Documentation
|
||||
==================
|
||||
|
||||
In Java, special comments before classes and functions function to
|
||||
document the code. A program to extract these, and turn them into
|
||||
HTML documentation is called javadoc, and is part of the standard
|
||||
Java distribution. However, the only output format that is supported
|
||||
is HTML, and JavaDoc has a very intimate relationship with HTML.
|
||||
|
||||
In Java, special comments before classes and functions function to
|
||||
document the code. A program to extract these, and turn them into
|
||||
HTML documentation is called javadoc, and is part of the standard
|
||||
Java distribution. However, the only output format that is supported
|
||||
is HTML, and JavaDoc has a very intimate relationship with HTML.
|
||||
|
||||
Python Docstring Goals
|
||||
======================
|
||||
|
||||
Python documentation string are easy to spot during parsing, and are
|
||||
also available to the runtime interpreter. This double purpose is
|
||||
a bit problematic, sometimes: for example, some are reluctant to have
|
||||
too long docstrings, because they do not want to take much space in
|
||||
the runtime. In addition, because of the current lack of tools, people
|
||||
read objects' docstrings by "print"ing them, so a tendency to make them
|
||||
brief and free of markups has sprung up. This tendency hinders writing
|
||||
better documentation-extraction tools, since it causes docstrings to
|
||||
contain little information, which is hard to parse.
|
||||
|
||||
Python documentation string are easy to spot during parsing, and are
|
||||
also available to the runtime interpreter. This double purpose is
|
||||
a bit problematic, sometimes: for example, some are reluctant to have
|
||||
too long docstrings, because they do not want to take much space in
|
||||
the runtime. In addition, because of the current lack of tools, people
|
||||
read objects' docstrings by "print"ing them, so a tendency to make them
|
||||
brief and free of markups has sprung up. This tendency hinders writing
|
||||
better documentation-extraction tools, since it causes docstrings to
|
||||
contain little information, which is hard to parse.
|
||||
|
||||
High Level Solutions
|
||||
====================
|
||||
|
||||
To counter the objection that the strings take up place in the running
|
||||
program, it is suggested that documentation extraction tools will
|
||||
concatenate a maximum prefix of string literals which appear in the
|
||||
beginning of a definition. The first of these will also be available
|
||||
in the interactive interpreter, so it should contain a few summary
|
||||
lines.
|
||||
|
||||
To counter the objection that the strings take up place in the running
|
||||
program, it is suggested that documentation extraction tools will
|
||||
concatenate a maximum prefix of string literals which appear in the
|
||||
beginning of a definition. The first of these will also be available
|
||||
in the interactive interpreter, so it should contain a few summary
|
||||
lines.
|
||||
|
||||
Docstring Format Goals
|
||||
======================
|
||||
|
||||
These are the goals for the docstring format, as discussed ad neasum
|
||||
in the doc-sig.
|
||||
These are the goals for the docstring format, as discussed ad nauseam
|
||||
in the doc-sig.
|
||||
|
||||
1. It must be easy to type with any standard text editor.
|
||||
2. It must be readable to the casual observer.
|
||||
3. It must not contain information which can be deduced from parsing
|
||||
the module.
|
||||
4. It must contain sufficient information so it can be converted
|
||||
to any reasonable markup format.
|
||||
5. It must be possible to write a module's entire documentation in
|
||||
docstrings, without feeling hampered by the markup language.
|
||||
|
||||
1. It must be easy to type with any standard text editor.
|
||||
2. It must be readable to the casual observer.
|
||||
3. It must not contain information which can be deduced from parsing
|
||||
the module.
|
||||
4. It must contain sufficient information so it can be converted
|
||||
to any reasonable markup format.
|
||||
5. It must be possible to write a module's entire documentation in
|
||||
docstrings, without feeling hampered by the markup language.
|
||||
|
||||
Docstring Contents
|
||||
==================
|
||||
|
||||
For requirement 5. above, it is needed to specify what must be
|
||||
in docstrings.
|
||||
For requirement 5. above, it is needed to specify what must be
|
||||
in docstrings.
|
||||
|
||||
At least the following must be available:
|
||||
At least the following must be available:
|
||||
|
||||
a. A tag that means "this is a Python ``something'', guess what"
|
||||
a. A tag that means "this is a Python `something`, guess what"
|
||||
|
||||
Example: In the sentence "The POP3 class", we need to markup "POP3"
|
||||
so. The parser will be able to guess it is a class from the contents
|
||||
of the poplib module, but we need to make it guess.
|
||||
Example: In the sentence "The POP3 class", we need to markup "POP3"
|
||||
so. The parser will be able to guess it is a class from the contents
|
||||
of the ``poplib`` module, but we need to make it guess.
|
||||
|
||||
b. Tags that mean "this is a Python class/module/class var/instance var..."
|
||||
b. Tags that mean "this is a Python class/module/class var/instance var..."
|
||||
|
||||
Example: The usual Python idiom for singleton class A is to have _A
|
||||
as the class, and A a function which returns _A objects. It's usual
|
||||
to document the class, nonetheless, as being A. This requires the
|
||||
strength to say "The class A" and have A hyperlinked and marked-up
|
||||
as a class.
|
||||
Example: The usual Python idiom for singleton class ``A`` is to have ``_A``
|
||||
as the class, and ``A`` a function which returns ``_A`` objects. It's usual
|
||||
to document the class, nonetheless, as being ``A``. This requires the
|
||||
strength to say "The class ``A``" and have ``A`` hyperlinked and marked-up
|
||||
as a class.
|
||||
|
||||
c. An easy way to include Python source code/Python interactive sessions
|
||||
|
||||
d. Emphasis/bold
|
||||
|
||||
e. List/tables
|
||||
|
||||
c. An easy way to include Python source code/Python interactive sessions
|
||||
d. Emphasis/bold
|
||||
e. List/tables
|
||||
|
||||
Docstring Basic Structure
|
||||
=========================
|
||||
|
||||
The documentation strings will be in StructuredTextNG
|
||||
(http://www.zope.org/Members/jim/StructuredTextWiki/StructuredTextNG)
|
||||
Since StructuredText is not yet strong enough to handle (a) and (b)
|
||||
above, we will need to extend it. I suggest using
|
||||
``[<optional description>:python identifier]``.
|
||||
E.g.: ``[class:POP3]``, ``[:POP3.list]``, etc. If the description is missing,
|
||||
a guess will be made from the text.
|
||||
|
||||
The documentation strings will be in StructuredTextNG
|
||||
(http://www.zope.org/Members/jim/StructuredTextWiki/StructuredTextNG)
|
||||
Since StructuredText is not yet strong enough to handle (a) and (b)
|
||||
above, we will need to extend it. I suggest using
|
||||
'[<optional description>:python identifier]'.
|
||||
E.g.: [class:POP3], [:POP3.list], etc. If the description is missing,
|
||||
a guess will be made from the text.
|
||||
|
||||
Unresolved Issues
|
||||
|
||||
Is there a way to escape characters in ST? If so, how?
|
||||
(example: * at the beginning of a line without being bullet symbol)
|
||||
=================
|
||||
|
||||
Is my suggestion above for Python symbols compatible with ST-NG?
|
||||
How hard would it be to extend ST-NG to support it?
|
||||
Is there a way to escape characters in ST? If so, how?
|
||||
(example: * at the beginning of a line without being bullet symbol)
|
||||
|
||||
How do we describe input and output types of functions?
|
||||
Is my suggestion above for Python symbols compatible with ST-NG?
|
||||
How hard would it be to extend ST-NG to support it?
|
||||
|
||||
What additional constraint do we enforce on each docstring?
|
||||
(module/class/function)?
|
||||
How do we describe input and output types of functions?
|
||||
|
||||
What additional constraint do we enforce on each docstring?
|
||||
(module/class/function)?
|
||||
|
||||
What are the guesser rules?
|
||||
|
||||
What are the guesser rules?
|
||||
|
||||
Rejected Suggestions
|
||||
====================
|
||||
|
||||
XML -- it's very hard to type, and too cluttered to read it
|
||||
comfortably.
|
||||
XML -- it's very hard to type, and too cluttered to read it comfortably.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
|
282
pep-0219.txt
282
pep-0219.txt
|
@ -5,187 +5,195 @@ Last-Modified: $Date$
|
|||
Author: gmcm@hypernet.com (Gordon McMillan)
|
||||
Status: Deferred
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 14-Aug-2000
|
||||
Python-Version: 2.1
|
||||
Post-History:
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This PEP discusses changes required to core Python in order to
|
||||
efficiently support generators, microthreads and coroutines. It is
|
||||
related to PEP 220, which describes how Python should be extended
|
||||
to support these facilities. The focus of this PEP is strictly on
|
||||
the changes required to allow these extensions to work.
|
||||
This PEP discusses changes required to core Python in order to
|
||||
efficiently support generators, microthreads and coroutines. It is
|
||||
related to PEP 220, which describes how Python should be extended
|
||||
to support these facilities. The focus of this PEP is strictly on
|
||||
the changes required to allow these extensions to work.
|
||||
|
||||
While these PEPs are based on Christian Tismer's Stackless[1]
|
||||
implementation, they do not regard Stackless as a reference
|
||||
implementation. Stackless (with an extension module) implements
|
||||
continuations, and from continuations one can implement
|
||||
coroutines, microthreads (as has been done by Will Ware[2]) and
|
||||
generators. But in more that a year, no one has found any other
|
||||
productive use of continuations, so there seems to be no demand
|
||||
for their support.
|
||||
While these PEPs are based on Christian Tismer's Stackless [1]_
|
||||
implementation, they do not regard Stackless as a reference
|
||||
implementation. Stackless (with an extension module) implements
|
||||
continuations, and from continuations one can implement
|
||||
coroutines, microthreads (as has been done by Will Ware [2]_) and
|
||||
generators. But in more that a year, no one has found any other
|
||||
productive use of continuations, so there seems to be no demand
|
||||
for their support.
|
||||
|
||||
However, Stackless support for continuations is a relatively minor
|
||||
piece of the implementation, so one might regard it as "a"
|
||||
reference implementation (rather than "the" reference
|
||||
implementation).
|
||||
However, Stackless support for continuations is a relatively minor
|
||||
piece of the implementation, so one might regard it as "a"
|
||||
reference implementation (rather than "the" reference
|
||||
implementation).
|
||||
|
||||
|
||||
Background
|
||||
==========
|
||||
|
||||
Generators and coroutines have been implemented in a number of
|
||||
languages in a number of ways. Indeed, Tim Peters has done pure
|
||||
Python implementations of generators[3] and coroutines[4] using
|
||||
threads (and a thread-based coroutine implementation exists for
|
||||
Java). However, the horrendous overhead of a thread-based
|
||||
implementation severely limits the usefulness of this approach.
|
||||
Generators and coroutines have been implemented in a number of
|
||||
languages in a number of ways. Indeed, Tim Peters has done pure
|
||||
Python implementations of generators [3]_ and coroutines [4]_ using
|
||||
threads (and a thread-based coroutine implementation exists for
|
||||
Java). However, the horrendous overhead of a thread-based
|
||||
implementation severely limits the usefulness of this approach.
|
||||
|
||||
Microthreads (a.k.a "green" or "user" threads) and coroutines
|
||||
involve transfers of control that are difficult to accommodate in
|
||||
a language implementation based on a single stack. (Generators can
|
||||
be done on a single stack, but they can also be regarded as a very
|
||||
simple case of coroutines.)
|
||||
Microthreads (a.k.a "green" or "user" threads) and coroutines
|
||||
involve transfers of control that are difficult to accommodate in
|
||||
a language implementation based on a single stack. (Generators can
|
||||
be done on a single stack, but they can also be regarded as a very
|
||||
simple case of coroutines.)
|
||||
|
||||
Real threads allocate a full-sized stack for each thread of
|
||||
control, and this is the major source of overhead. However,
|
||||
coroutines and microthreads can be implemented in Python in a way
|
||||
that involves almost no overhead. This PEP, therefor, offers a
|
||||
way for making Python able to realistically manage thousands of
|
||||
separate "threads" of activity (vs. today's limit of perhaps dozens
|
||||
of separate threads of activity).
|
||||
Real threads allocate a full-sized stack for each thread of
|
||||
control, and this is the major source of overhead. However,
|
||||
coroutines and microthreads can be implemented in Python in a way
|
||||
that involves almost no overhead. This PEP, therefor, offers a
|
||||
way for making Python able to realistically manage thousands of
|
||||
separate "threads" of activity (vs. today's limit of perhaps dozens
|
||||
of separate threads of activity).
|
||||
|
||||
Another justification for this PEP (explored in PEP 220) is that
|
||||
coroutines and generators often allow a more direct expression of
|
||||
an algorithm than is possible in today's Python.
|
||||
Another justification for this PEP (explored in PEP 220) is that
|
||||
coroutines and generators often allow a more direct expression of
|
||||
an algorithm than is possible in today's Python.
|
||||
|
||||
|
||||
Discussion
|
||||
==========
|
||||
|
||||
The first thing to note is that Python, while it mingles
|
||||
interpreter data (normal C stack usage) with Python data (the
|
||||
state of the interpreted program) on the stack, the two are
|
||||
logically separate. They just happen to use the same stack.
|
||||
The first thing to note is that Python, while it mingles
|
||||
interpreter data (normal C stack usage) with Python data (the
|
||||
state of the interpreted program) on the stack, the two are
|
||||
logically separate. They just happen to use the same stack.
|
||||
|
||||
A real thread gets something approaching a process-sized stack
|
||||
because the implementation has no way of knowing how much stack
|
||||
space the thread will require. The stack space required for an
|
||||
individual frame is likely to be reasonable, but stack switching
|
||||
is an arcane and non-portable process, not supported by C.
|
||||
A real thread gets something approaching a process-sized stack
|
||||
because the implementation has no way of knowing how much stack
|
||||
space the thread will require. The stack space required for an
|
||||
individual frame is likely to be reasonable, but stack switching
|
||||
is an arcane and non-portable process, not supported by C.
|
||||
|
||||
Once Python stops putting Python data on the C stack, however,
|
||||
stack switching becomes easy.
|
||||
Once Python stops putting Python data on the C stack, however,
|
||||
stack switching becomes easy.
|
||||
|
||||
The fundamental approach of the PEP is based on these two
|
||||
ideas. First, separate C's stack usage from Python's stack
|
||||
usage. Secondly, associate with each frame enough stack space to
|
||||
handle that frame's execution.
|
||||
The fundamental approach of the PEP is based on these two
|
||||
ideas. First, separate C's stack usage from Python's stack
|
||||
usage. Secondly, associate with each frame enough stack space to
|
||||
handle that frame's execution.
|
||||
|
||||
In the normal usage, Stackless Python has a normal stack
|
||||
structure, except that it is broken into chunks. But in the
|
||||
presence of a coroutine / microthread extension, this same
|
||||
mechanism supports a stack with a tree structure. That is, an
|
||||
extension can support transfers of control between frames outside
|
||||
the normal "call / return" path.
|
||||
In the normal usage, Stackless Python has a normal stack
|
||||
structure, except that it is broken into chunks. But in the
|
||||
presence of a coroutine / microthread extension, this same
|
||||
mechanism supports a stack with a tree structure. That is, an
|
||||
extension can support transfers of control between frames outside
|
||||
the normal "call / return" path.
|
||||
|
||||
|
||||
Problems
|
||||
========
|
||||
|
||||
The major difficulty with this approach is C calling Python. The
|
||||
problem is that the C stack now holds a nested execution of the
|
||||
byte-code interpreter. In that situation, a coroutine /
|
||||
microthread extension cannot be permitted to transfer control to a
|
||||
frame in a different invocation of the byte-code interpreter. If a
|
||||
frame were to complete and exit back to C from the wrong
|
||||
interpreter, the C stack could be trashed.
|
||||
The major difficulty with this approach is C calling Python. The
|
||||
problem is that the C stack now holds a nested execution of the
|
||||
byte-code interpreter. In that situation, a coroutine /
|
||||
microthread extension cannot be permitted to transfer control to a
|
||||
frame in a different invocation of the byte-code interpreter. If a
|
||||
frame were to complete and exit back to C from the wrong
|
||||
interpreter, the C stack could be trashed.
|
||||
|
||||
The ideal solution is to create a mechanism where nested
|
||||
executions of the byte code interpreter are never needed. The easy
|
||||
solution is for the coroutine / microthread extension(s) to
|
||||
recognize the situation and refuse to allow transfers outside the
|
||||
current invocation.
|
||||
The ideal solution is to create a mechanism where nested
|
||||
executions of the byte code interpreter are never needed. The easy
|
||||
solution is for the coroutine / microthread extension(s) to
|
||||
recognize the situation and refuse to allow transfers outside the
|
||||
current invocation.
|
||||
|
||||
We can categorize code that involves C calling Python into two
|
||||
camps: Python's implementation, and C extensions. And hopefully we
|
||||
can offer a compromise: Python's internal usage (and C extension
|
||||
writers who want to go to the effort) will no longer use a nested
|
||||
invocation of the interpreter. Extensions which do not go to the
|
||||
effort will still be safe, but will not play well with coroutines
|
||||
/ microthreads.
|
||||
We can categorize code that involves C calling Python into two
|
||||
camps: Python's implementation, and C extensions. And hopefully we
|
||||
can offer a compromise: Python's internal usage (and C extension
|
||||
writers who want to go to the effort) will no longer use a nested
|
||||
invocation of the interpreter. Extensions which do not go to the
|
||||
effort will still be safe, but will not play well with coroutines
|
||||
/ microthreads.
|
||||
|
||||
Generally, when a recursive call is transformed into a loop, a bit
|
||||
of extra bookkeeping is required. The loop will need to keep its
|
||||
own "stack" of arguments and results since the real stack can now
|
||||
only hold the most recent. The code will be more verbose, because
|
||||
it's not quite as obvious when we're done. While Stackless is not
|
||||
implemented this way, it has to deal with the same issues.
|
||||
Generally, when a recursive call is transformed into a loop, a bit
|
||||
of extra bookkeeping is required. The loop will need to keep its
|
||||
own "stack" of arguments and results since the real stack can now
|
||||
only hold the most recent. The code will be more verbose, because
|
||||
it's not quite as obvious when we're done. While Stackless is not
|
||||
implemented this way, it has to deal with the same issues.
|
||||
|
||||
In normal Python, PyEval_EvalCode is used to build a frame and
|
||||
execute it. Stackless Python introduces the concept of a
|
||||
FrameDispatcher. Like PyEval_EvalCode, it executes one frame. But
|
||||
the interpreter may signal the FrameDispatcher that a new frame
|
||||
has been swapped in, and the new frame should be executed. When a
|
||||
frame completes, the FrameDispatcher follows the back pointer to
|
||||
resume the "calling" frame.
|
||||
In normal Python, ``PyEval_EvalCode`` is used to build a frame and
|
||||
execute it. Stackless Python introduces the concept of a
|
||||
``FrameDispatcher``. Like ``PyEval_EvalCode``, it executes one frame. But
|
||||
the interpreter may signal the ``FrameDispatcher`` that a new frame
|
||||
has been swapped in, and the new frame should be executed. When a
|
||||
frame completes, the ``FrameDispatcher`` follows the back pointer to
|
||||
resume the "calling" frame.
|
||||
|
||||
So Stackless transforms recursions into a loop, but it is not the
|
||||
FrameDispatcher that manages the frames. This is done by the
|
||||
interpreter (or an extension that knows what it's doing).
|
||||
So Stackless transforms recursions into a loop, but it is not the
|
||||
``FrameDispatcher`` that manages the frames. This is done by the
|
||||
interpreter (or an extension that knows what it's doing).
|
||||
|
||||
The general idea is that where C code needs to execute Python
|
||||
code, it creates a frame for the Python code, setting its back
|
||||
pointer to the current frame. Then it swaps in the frame, signals
|
||||
the FrameDispatcher and gets out of the way. The C stack is now
|
||||
clean - the Python code can transfer control to any other frame
|
||||
(if an extension gives it the means to do so).
|
||||
The general idea is that where C code needs to execute Python
|
||||
code, it creates a frame for the Python code, setting its back
|
||||
pointer to the current frame. Then it swaps in the frame, signals
|
||||
the ``FrameDispatcher`` and gets out of the way. The C stack is now
|
||||
clean - the Python code can transfer control to any other frame
|
||||
(if an extension gives it the means to do so).
|
||||
|
||||
In the vanilla case, this magic can be hidden from the programmer
|
||||
(even, in most cases, from the Python-internals programmer). Many
|
||||
situations present another level of difficulty, however.
|
||||
In the vanilla case, this magic can be hidden from the programmer
|
||||
(even, in most cases, from the Python-internals programmer). Many
|
||||
situations present another level of difficulty, however.
|
||||
|
||||
The map builtin function involves two obstacles to this
|
||||
approach. It cannot simply construct a frame and get out of the
|
||||
way, not just because there's a loop involved, but each pass
|
||||
through the loop requires some "post" processing. In order to play
|
||||
well with others, Stackless constructs a frame object for map
|
||||
itself.
|
||||
The map builtin function involves two obstacles to this
|
||||
approach. It cannot simply construct a frame and get out of the
|
||||
way, not just because there's a loop involved, but each pass
|
||||
through the loop requires some "post" processing. In order to play
|
||||
well with others, Stackless constructs a frame object for map
|
||||
itself.
|
||||
|
||||
Most recursions of the interpreter are not this complex, but
|
||||
fairly frequently, some "post" operations are required. Stackless
|
||||
does not fix these situations because of amount of code changes
|
||||
required. Instead, Stackless prohibits transfers out of a nested
|
||||
interpreter. While not ideal (and sometimes puzzling), this
|
||||
limitation is hardly crippling.
|
||||
|
||||
Most recursions of the interpreter are not this complex, but
|
||||
fairly frequently, some "post" operations are required. Stackless
|
||||
does not fix these situations because of amount of code changes
|
||||
required. Instead, Stackless prohibits transfers out of a nested
|
||||
interpreter. While not ideal (and sometimes puzzling), this
|
||||
limitation is hardly crippling.
|
||||
|
||||
|
||||
Advantages
|
||||
==========
|
||||
|
||||
For normal Python, the advantage to this approach is that C stack
|
||||
usage becomes much smaller and more predictable. Unbounded
|
||||
recursion in Python code becomes a memory error, instead of a
|
||||
stack error (and thus, in non-Cupertino operating systems,
|
||||
something that can be recovered from). The price, of course, is
|
||||
the added complexity that comes from transforming recursions of
|
||||
the byte-code interpreter loop into a higher order loop (and the
|
||||
attendant bookkeeping involved).
|
||||
For normal Python, the advantage to this approach is that C stack
|
||||
usage becomes much smaller and more predictable. Unbounded
|
||||
recursion in Python code becomes a memory error, instead of a
|
||||
stack error (and thus, in non-Cupertino operating systems,
|
||||
something that can be recovered from). The price, of course, is
|
||||
the added complexity that comes from transforming recursions of
|
||||
the byte-code interpreter loop into a higher order loop (and the
|
||||
attendant bookkeeping involved).
|
||||
|
||||
The big advantage comes from realizing that the Python stack is
|
||||
really a tree, and the frame dispatcher can transfer control
|
||||
freely between leaf nodes of the tree, thus allowing things like
|
||||
microthreads and coroutines.
|
||||
The big advantage comes from realizing that the Python stack is
|
||||
really a tree, and the frame dispatcher can transfer control
|
||||
freely between leaf nodes of the tree, thus allowing things like
|
||||
microthreads and coroutines.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] http://www.stackless.com
|
||||
[2] http://world.std.com/~wware/uthread.html
|
||||
[3] Demo/threads/Generator.py in the source distribution
|
||||
[4] http://www.stackless.com/coroutines.tim.peters.html
|
||||
.. [1] http://www.stackless.com
|
||||
.. [2] http://web.archive.org/web/20000815070602/http://world.std.com/~wware/uthread.html
|
||||
.. [3] Demo/threads/Generator.py in the source distribution
|
||||
.. [4] http://www.stackless.com/coroutines.tim.peters.html
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
|
174
pep-0228.txt
174
pep-0228.txt
|
@ -5,140 +5,152 @@ Last-Modified: $Date$
|
|||
Author: moshez@zadka.site.co.il (Moshe Zadka), guido@python.org (Guido van Rossum)
|
||||
Status: Withdrawn
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 4-Nov-2000
|
||||
Python-Version: ??
|
||||
Post-History:
|
||||
|
||||
|
||||
Withdrawal
|
||||
==========
|
||||
|
||||
This PEP has been withdrawn in favor of PEP 3141.
|
||||
This PEP has been withdrawn in favor of PEP 3141.
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
Today, Python's numerical model is similar to the C numeric model:
|
||||
there are several unrelated numerical types, and when operations
|
||||
between numerical types are requested, coercions happen. While
|
||||
the C rationale for the numerical model is that it is very similar
|
||||
to what happens at the hardware level, that rationale does not
|
||||
apply to Python. So, while it is acceptable to C programmers that
|
||||
2/3 == 0, it is surprising to many Python programmers.
|
||||
Today, Python's numerical model is similar to the C numeric model:
|
||||
there are several unrelated numerical types, and when operations
|
||||
between numerical types are requested, coercions happen. While
|
||||
the C rationale for the numerical model is that it is very similar
|
||||
to what happens at the hardware level, that rationale does not
|
||||
apply to Python. So, while it is acceptable to C programmers that
|
||||
2/3 == 0, it is surprising to many Python programmers.
|
||||
|
||||
NOTE: in the light of recent discussions in the newsgroup, the
|
||||
motivation in this PEP (and details) need to be extended.
|
||||
NOTE: in the light of recent discussions in the newsgroup, the
|
||||
motivation in this PEP (and details) need to be extended.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
In usability studies, one of the least usable aspect of Python was
|
||||
the fact that integer division returns the floor of the division.
|
||||
This makes it hard to program correctly, requiring casts to
|
||||
float() in various parts through the code. Python's numerical
|
||||
model stems from C, while a model that might be easier to work with
|
||||
can be based on the mathematical understanding of numbers.
|
||||
In usability studies, one of the least usable aspect of Python was
|
||||
the fact that integer division returns the floor of the division.
|
||||
This makes it hard to program correctly, requiring casts to
|
||||
``float()`` in various parts through the code. Python's numerical
|
||||
model stems from C, while a model that might be easier to work with
|
||||
can be based on the mathematical understanding of numbers.
|
||||
|
||||
|
||||
Other Numerical Models
|
||||
======================
|
||||
|
||||
Perl's numerical model is that there is one type of numbers --
|
||||
floating point numbers. While it is consistent and superficially
|
||||
non-surprising, it tends to have subtle gotchas. One of these is
|
||||
that printing numbers is very tricky, and requires correct
|
||||
rounding. In Perl, there is also a mode where all numbers are
|
||||
integers. This mode also has its share of problems, which arise
|
||||
from the fact that there is not even an approximate way of
|
||||
dividing numbers and getting meaningful answers.
|
||||
Perl's numerical model is that there is one type of numbers --
|
||||
floating point numbers. While it is consistent and superficially
|
||||
non-surprising, it tends to have subtle gotchas. One of these is
|
||||
that printing numbers is very tricky, and requires correct
|
||||
rounding. In Perl, there is also a mode where all numbers are
|
||||
integers. This mode also has its share of problems, which arise
|
||||
from the fact that there is not even an approximate way of
|
||||
dividing numbers and getting meaningful answers.
|
||||
|
||||
|
||||
Suggested Interface For Python's Numerical Model
|
||||
================================================
|
||||
|
||||
While coercion rules will remain for add-on types and classes, the
|
||||
built in type system will have exactly one Python type -- a
|
||||
number. There are several things which can be considered "number
|
||||
methods":
|
||||
While coercion rules will remain for add-on types and classes, the
|
||||
built in type system will have exactly one Python type -- a
|
||||
number. There are several things which can be considered "number
|
||||
methods":
|
||||
|
||||
1. isnatural()
|
||||
2. isintegral()
|
||||
3. isrational()
|
||||
4. isreal()
|
||||
5. iscomplex()
|
||||
1. ``isnatural()``
|
||||
2. ``isintegral()``
|
||||
3. ``isrational()``
|
||||
4. ``isreal()``
|
||||
5. ``iscomplex()``
|
||||
6. ``isexact()``
|
||||
|
||||
a. isexact()
|
||||
Obviously, a number which answers true to a question from 1 to 5, will
|
||||
also answer true to any following question. If ``isexact()`` is not true,
|
||||
then any answer might be wrong.
|
||||
(But not horribly wrong: it's close to the truth.)
|
||||
|
||||
Obviously, a number which answers true to a question from 1 to 5, will
|
||||
also answer true to any following question. If "isexact()" is not true,
|
||||
then any answer might be wrong.
|
||||
(But not horribly wrong: it's close to the truth.)
|
||||
Now, there is two thing the models promises for the field operations
|
||||
(+, -, /, \*):
|
||||
|
||||
Now, there is two thing the models promises for the field operations
|
||||
(+, -, /, *):
|
||||
- If both operands satisfy ``isexact()``, the result satisfies
|
||||
``isexact()``.
|
||||
|
||||
- If both operands satisfy isexact(), the result satisfies
|
||||
isexact().
|
||||
- All field rules are true, except that for not-``isexact()`` numbers,
|
||||
they might be only approximately true.
|
||||
|
||||
- All field rules are true, except that for not-isexact() numbers,
|
||||
they might be only approximately true.
|
||||
One consequence of these two rules is that all exact calcutions
|
||||
are done as (complex) rationals: since the field laws must hold,
|
||||
then::
|
||||
|
||||
One consequence of these two rules is that all exact calcutions
|
||||
are done as (complex) rationals: since the field laws must hold,
|
||||
then
|
||||
(a/b)*b == a
|
||||
|
||||
(a/b)*b == a
|
||||
must hold.
|
||||
|
||||
must hold.
|
||||
There is built-in function, ``inexact()`` which takes a number
|
||||
and returns an inexact number which is a good approximation.
|
||||
Inexact numbers must be as least as accurate as if they were
|
||||
using IEEE-754.
|
||||
|
||||
There is built-in function, inexact() which takes a number
|
||||
and returns an inexact number which is a good approximation.
|
||||
Inexact numbers must be as least as accurate as if they were
|
||||
using IEEE-754.
|
||||
Several of the classical Python functions will return exact numbers
|
||||
even when given inexact numbers: e.g, ``int()``.
|
||||
|
||||
Several of the classical Python functions will return exact numbers
|
||||
even when given inexact numbers: e.g, int().
|
||||
|
||||
Coercion
|
||||
========
|
||||
|
||||
The number type does not define ``nb_coerce``
|
||||
Any numeric operation slot, when receiving something other then ``PyNumber``,
|
||||
refuses to implement it.
|
||||
|
||||
The number type does not define nb_coerce
|
||||
Any numeric operation slot, when receiving something other then PyNumber,
|
||||
refuses to implement it.
|
||||
|
||||
Inexact Operations
|
||||
==================
|
||||
|
||||
The functions in the "math" module will be allowed to return
|
||||
inexact results for exact values. However, they will never return
|
||||
a non-real number. The functions in the "cmath" module are also
|
||||
allowed to return an inexact result for an exact argument, and are
|
||||
furthermore allowed to return a complex result for a real
|
||||
argument.
|
||||
The functions in the ``math`` module will be allowed to return
|
||||
inexact results for exact values. However, they will never return
|
||||
a non-real number. The functions in the ``cmath`` module are also
|
||||
allowed to return an inexact result for an exact argument, and are
|
||||
furthermore allowed to return a complex result for a real
|
||||
argument.
|
||||
|
||||
|
||||
Numerical Python Issues
|
||||
=======================
|
||||
|
||||
People who use Numerical Python do so for high-performance vector
|
||||
operations. Therefore, NumPy should keep its hardware based
|
||||
numeric model.
|
||||
People who use Numerical Python do so for high-performance vector
|
||||
operations. Therefore, NumPy should keep its hardware based
|
||||
numeric model.
|
||||
|
||||
|
||||
Unresolved Issues
|
||||
=================
|
||||
|
||||
Which number literals will be exact, and which inexact?
|
||||
Which number literals will be exact, and which inexact?
|
||||
|
||||
How do we deal with IEEE 754 operations? (probably, isnan/isinf should
|
||||
be methods)
|
||||
How do we deal with IEEE 754 operations? (probably, isnan/isinf should
|
||||
be methods)
|
||||
|
||||
On 64-bit machines, comparisons between ints and floats may be
|
||||
broken when the comparison involves conversion to float. Ditto
|
||||
for comparisons between longs and floats. This can be dealt with
|
||||
by avoiding the conversion to float. (Due to Andrew Koenig.)
|
||||
On 64-bit machines, comparisons between ints and floats may be
|
||||
broken when the comparison involves conversion to float. Ditto
|
||||
for comparisons between longs and floats. This can be dealt with
|
||||
by avoiding the conversion to float. (Due to Andrew Koenig.)
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
|
202
pep-0235.txt
202
pep-0235.txt
|
@ -5,27 +5,30 @@ Last-Modified: $Date$
|
|||
Author: Tim Peters <tim.peters@gmail.com>
|
||||
Status: Final
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created:
|
||||
Python-Version: 2.1
|
||||
Post-History: 16 February 2001
|
||||
|
||||
|
||||
Note
|
||||
====
|
||||
|
||||
This is essentially a retroactive PEP: the issue came up too late
|
||||
in the 2.1 release process to solicit wide opinion before deciding
|
||||
what to do, and can't be put off until 2.2 without also delaying
|
||||
the Cygwin and MacOS X ports.
|
||||
This is essentially a retroactive PEP: the issue came up too late
|
||||
in the 2.1 release process to solicit wide opinion before deciding
|
||||
what to do, and can't be put off until 2.2 without also delaying
|
||||
the Cygwin and MacOS X ports.
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
File systems vary across platforms in whether or not they preserve
|
||||
the case of filenames, and in whether or not the platform C
|
||||
library file-opening functions do or don't insist on
|
||||
case-sensitive matches:
|
||||
File systems vary across platforms in whether or not they preserve
|
||||
the case of filenames, and in whether or not the platform C
|
||||
library file-opening functions do or don't insist on
|
||||
case-sensitive matches::
|
||||
|
||||
case-preserving case-destroying
|
||||
case-preserving case-destroying
|
||||
+-------------------+------------------+
|
||||
case-sensitive | most Unix flavors | brrrrrrrrrr |
|
||||
+-------------------+------------------+
|
||||
|
@ -35,119 +38,122 @@ Motivation
|
|||
| | OpenVMS |
|
||||
+-------------------+------------------+
|
||||
|
||||
In the upper left box, if you create "fiLe" it's stored as "fiLe",
|
||||
and only open("fiLe") will open it (open("file") will not, nor
|
||||
will the 14 other variations on that theme).
|
||||
In the upper left box, if you create "fiLe" it's stored as "fiLe",
|
||||
and only ``open("fiLe")`` will open it ``(open("file")`` will not, nor
|
||||
will the 14 other variations on that theme).
|
||||
|
||||
In the lower right box, if you create "fiLe", there's no telling
|
||||
what it's stored as -- but most likely as "FILE" -- and any of the
|
||||
16 obvious variations on open("FilE") will open it.
|
||||
In the lower right box, if you create "fiLe", there's no telling
|
||||
what it's stored as -- but most likely as "FILE" -- and any of the
|
||||
16 obvious variations on ``open("FilE")`` will open it.
|
||||
|
||||
The lower left box is a mix: creating "fiLe" stores "fiLe" in the
|
||||
platform directory, but you don't have to match case when opening
|
||||
it; any of the 16 obvious variations on open("FILe") work.
|
||||
The lower left box is a mix: creating "fiLe" stores "fiLe" in the
|
||||
platform directory, but you don't have to match case when opening
|
||||
it; any of the 16 obvious variations on ``open("FILe")`` work.
|
||||
|
||||
NONE OF THAT IS CHANGING! Python will continue to follow platform
|
||||
conventions w.r.t. whether case is preserved when creating a file,
|
||||
and w.r.t. whether open() requires a case-sensitive match. In
|
||||
practice, you should always code as if matches were
|
||||
case-sensitive, else your program won't be portable.
|
||||
NONE OF THAT IS CHANGING! Python will continue to follow platform
|
||||
conventions w.r.t. whether case is preserved when creating a file,
|
||||
and w.r.t. whether ``open()`` requires a case-sensitive match. In
|
||||
practice, you should always code as if matches were
|
||||
case-sensitive, else your program won't be portable.
|
||||
|
||||
What's proposed is to change the semantics of Python "import"
|
||||
statements, and there *only* in the lower left box.
|
||||
What's proposed is to change the semantics of Python "import"
|
||||
statements, and there *only* in the lower left box.
|
||||
|
||||
|
||||
Current Lower-Left Semantics
|
||||
============================
|
||||
|
||||
Support for MacOSX HFS+, and for Cygwin, is new in 2.1, so nothing
|
||||
is changing there. What's changing is Windows behavior. Here are
|
||||
the current rules for import on Windows:
|
||||
Support for MacOSX HFS+, and for Cygwin, is new in 2.1, so nothing
|
||||
is changing there. What's changing is Windows behavior. Here are
|
||||
the current rules for import on Windows:
|
||||
|
||||
1. Despite that the filesystem is case-insensitive, Python insists
|
||||
on a case-sensitive match. But not in the way the upper left
|
||||
box works: if you have two files, FiLe.py and file.py on
|
||||
sys.path, and do
|
||||
1. Despite that the filesystem is case-insensitive, Python insists
|
||||
on a case-sensitive match. But not in the way the upper left
|
||||
box works: if you have two files, ``FiLe.py`` and ``file.py`` on
|
||||
``sys.path``, and do::
|
||||
|
||||
import file
|
||||
import file
|
||||
|
||||
then if Python finds FiLe.py first, it raises a NameError. It
|
||||
does *not* go on to find file.py; indeed, it's impossible to
|
||||
import any but the first case-insensitive match on sys.path,
|
||||
and then only if case matches exactly in the first
|
||||
case-insensitive match.
|
||||
then if Python finds ``FiLe.py`` first, it raises a ``NameError``. It
|
||||
does *not* go on to find ``file.py``; indeed, it's impossible to
|
||||
import any but the first case-insensitive match on ``sys.path``,
|
||||
and then only if case matches exactly in the first
|
||||
case-insensitive match.
|
||||
|
||||
2. An ugly exception: if the first case-insensitive match on
|
||||
sys.path is for a file whose name is entirely in upper case
|
||||
(FILE.PY or FILE.PYC or FILE.PYO), then the import silently
|
||||
grabs that, no matter what mixture of case was used in the
|
||||
import statement. This is apparently to cater to miserable old
|
||||
filesystems that really fit in the lower right box. But this
|
||||
exception is unique to Windows, for reasons that may or may not
|
||||
exist.
|
||||
2. An ugly exception: if the first case-insensitive match on
|
||||
``sys.path`` is for a file whose name is entirely in upper case
|
||||
(``FILE.PY`` or ``FILE.PYC`` or ``FILE.PYO``), then the import silently
|
||||
grabs that, no matter what mixture of case was used in the
|
||||
import statement. This is apparently to cater to miserable old
|
||||
filesystems that really fit in the lower right box. But this
|
||||
exception is unique to Windows, for reasons that may or may not
|
||||
exist.
|
||||
|
||||
3. And another exception: if the environment variable PYTHONCASEOK
|
||||
exists, Python silently grabs the first case-insensitive match
|
||||
of any kind.
|
||||
3. And another exception: if the environment variable ``PYTHONCASEOK``
|
||||
exists, Python silently grabs the first case-insensitive match
|
||||
of any kind.
|
||||
|
||||
So these Windows rules are pretty complicated, and neither match
|
||||
the Unix rules nor provide semantics natural for the native
|
||||
filesystem. That makes them hard to explain to Unix *or* Windows
|
||||
users. Nevertheless, they've worked fine for years, and in
|
||||
isolation there's no compelling reason to change them.
|
||||
So these Windows rules are pretty complicated, and neither match
|
||||
the Unix rules nor provide semantics natural for the native
|
||||
filesystem. That makes them hard to explain to Unix *or* Windows
|
||||
users. Nevertheless, they've worked fine for years, and in
|
||||
isolation there's no compelling reason to change them.
|
||||
|
||||
However, that was before the MacOSX HFS+ and Cygwin ports arrived.
|
||||
They also have case-preserving case-insensitive filesystems, but
|
||||
the people doing the ports despised the Windows rules. Indeed, a
|
||||
patch to make HFS+ act like Unix for imports got past a reviewer
|
||||
and into the code base, which incidentally made Cygwin also act
|
||||
like Unix (but this met the unbounded approval of the Cygwin
|
||||
folks, so they sure didn't complain -- they had patches of their
|
||||
own pending to do this, but the reviewer for those balked).
|
||||
However, that was before the MacOSX HFS+ and Cygwin ports arrived.
|
||||
They also have case-preserving case-insensitive filesystems, but
|
||||
the people doing the ports despised the Windows rules. Indeed, a
|
||||
patch to make HFS+ act like Unix for imports got past a reviewer
|
||||
and into the code base, which incidentally made Cygwin also act
|
||||
like Unix (but this met the unbounded approval of the Cygwin
|
||||
folks, so they sure didn't complain -- they had patches of their
|
||||
own pending to do this, but the reviewer for those balked).
|
||||
|
||||
At a higher level, we want to keep Python consistent, by following
|
||||
the same rules on *all* platforms with case-preserving
|
||||
case-insensitive filesystems.
|
||||
At a higher level, we want to keep Python consistent, by following
|
||||
the same rules on *all* platforms with case-preserving
|
||||
case-insensitive filesystems.
|
||||
|
||||
|
||||
Proposed Semantics
|
||||
==================
|
||||
|
||||
The proposed new semantics for the lower left box:
|
||||
The proposed new semantics for the lower left box:
|
||||
|
||||
A. If the PYTHONCASEOK environment variable exists, same as
|
||||
before: silently accept the first case-insensitive match of any
|
||||
kind; raise ImportError if none found.
|
||||
A. If the ``PYTHONCASEOK`` environment variable exists, same as
|
||||
before: silently accept the first case-insensitive match of any
|
||||
kind; raise ImportError if none found.
|
||||
|
||||
B. Else search sys.path for the first case-sensitive match; raise
|
||||
ImportError if none found.
|
||||
B. Else search ``sys.path`` for the first case-sensitive match; raise
|
||||
``ImportError`` if none found.
|
||||
|
||||
#B is the same rule as is used on Unix, so this will improve cross-
|
||||
platform portability. That's good. #B is also the rule the Mac
|
||||
and Cygwin folks want (and wanted enough to implement themselves,
|
||||
multiple times, which is a powerful argument in PythonLand). It
|
||||
can't cause any existing non-exceptional Windows import to fail,
|
||||
because any existing non-exceptional Windows import finds a
|
||||
case-sensitive match first in the path -- and it still will. An
|
||||
exceptional Windows import currently blows up with a NameError or
|
||||
ImportError, in which latter case it still will, or in which
|
||||
former case will continue searching, and either succeed or blow up
|
||||
with an ImportError.
|
||||
#B is the same rule as is used on Unix, so this will improve cross-
|
||||
platform portability. That's good. #B is also the rule the Mac
|
||||
and Cygwin folks want (and wanted enough to implement themselves,
|
||||
multiple times, which is a powerful argument in PythonLand). It
|
||||
can't cause any existing non-exceptional Windows import to fail,
|
||||
because any existing non-exceptional Windows import finds a
|
||||
case-sensitive match first in the path -- and it still will. An
|
||||
exceptional Windows import currently blows up with a ``NameError`` or
|
||||
``ImportError``, in which latter case it still will, or in which
|
||||
former case will continue searching, and either succeed or blow up
|
||||
with an ``ImportError``.
|
||||
|
||||
#A is needed to cater to case-destroying filesystems mounted on Windows,
|
||||
and *may* also be used by people so enamored of "natural" Windows
|
||||
behavior that they're willing to set an environment variable to
|
||||
get it. I don't intend to implement #A for Unix too, but that's
|
||||
just because I'm not clear on how I *could* do so efficiently (I'm
|
||||
not going to slow imports under Unix just for theoretical purity).
|
||||
#A is needed to cater to case-destroying filesystems mounted on Windows,
|
||||
and *may* also be used by people so enamored of "natural" Windows
|
||||
behavior that they're willing to set an environment variable to
|
||||
get it. I don't intend to implement #A for Unix too, but that's
|
||||
just because I'm not clear on how I *could* do so efficiently (I'm
|
||||
not going to slow imports under Unix just for theoretical purity).
|
||||
|
||||
The potential damage is here: #2 (matching on ALLCAPS.PY) is
|
||||
proposed to be dropped. Case-destroying filesystems are a
|
||||
vanishing breed, and support for them is ugly. We're already
|
||||
supporting (and will continue to support) PYTHONCASEOK for their
|
||||
benefit, but they don't deserve multiple hacks in 2001.
|
||||
The potential damage is here: #2 (matching on ``ALLCAPS.PY``) is
|
||||
proposed to be dropped. Case-destroying filesystems are a
|
||||
vanishing breed, and support for them is ugly. We're already
|
||||
supporting (and will continue to support) ``PYTHONCASEOK`` for their
|
||||
benefit, but they don't deserve multiple hacks in 2001.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
|
276
pep-0269.txt
276
pep-0269.txt
|
@ -5,184 +5,216 @@ Last-Modified: $Date$
|
|||
Author: jriehl@spaceship.com (Jonathan Riehl)
|
||||
Status: Deferred
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 24-Aug-2001
|
||||
Python-Version: 2.2
|
||||
Post-History:
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
Much like the parser module exposes the Python parser, this PEP
|
||||
proposes that the parser generator used to create the Python
|
||||
parser, pgen, be exposed as a module in Python.
|
||||
Much like the parser module exposes the Python parser, this PEP
|
||||
proposes that the parser generator used to create the Python
|
||||
parser, ``pgen``, be exposed as a module in Python.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
Through the course of Pythonic history, there have been numerous
|
||||
discussions about the creation of a Python compiler [1]. These
|
||||
have resulted in several implementations of Python parsers, most
|
||||
notably the parser module currently provided in the Python
|
||||
standard library[2] and Jeremy Hylton's compiler module[3].
|
||||
However, while multiple language changes have been proposed
|
||||
[4][5], experimentation with the Python syntax has lacked the
|
||||
benefit of a Python binding to the actual parser generator used to
|
||||
build Python.
|
||||
Through the course of Pythonic history, there have been numerous
|
||||
discussions about the creation of a Python compiler [1]_. These
|
||||
have resulted in several implementations of Python parsers, most
|
||||
notably the parser module currently provided in the Python
|
||||
standard library [2]_ and Jeremy Hylton's compiler module [3]_.
|
||||
However, while multiple language changes have been proposed
|
||||
[4]_ [5]_, experimentation with the Python syntax has lacked the
|
||||
benefit of a Python binding to the actual parser generator used to
|
||||
build Python.
|
||||
|
||||
By providing a Python wrapper analogous to Fred Drake Jr.'s parser
|
||||
wrapper, but targeted at the pgen library, the following
|
||||
assertions are made:
|
||||
By providing a Python wrapper analogous to Fred Drake Jr.'s parser
|
||||
wrapper, but targeted at the ``pgen`` library, the following
|
||||
assertions are made:
|
||||
|
||||
1. Reference implementations of syntax changes will be easier to
|
||||
develop. Currently, a reference implementation of a syntax
|
||||
change would require the developer to use the pgen tool from
|
||||
the command line. The resulting parser data structure would
|
||||
then either have to be reworked to interface with a custom
|
||||
CPython implementation, or wrapped as a C extension module.
|
||||
1. Reference implementations of syntax changes will be easier to
|
||||
develop. Currently, a reference implementation of a syntax
|
||||
change would require the developer to use the ``pgen`` tool from
|
||||
the command line. The resulting parser data structure would
|
||||
then either have to be reworked to interface with a custom
|
||||
CPython implementation, or wrapped as a C extension module.
|
||||
|
||||
2. Reference implementations of syntax changes will be easier to
|
||||
distribute. Since the parser generator will be available in
|
||||
Python, it should follow that the resulting parser will
|
||||
accessible from Python. Therefore, reference implementations
|
||||
should be available as pure Python code, versus using custom
|
||||
versions of the existing CPython distribution, or as compilable
|
||||
extension modules.
|
||||
2. Reference implementations of syntax changes will be easier to
|
||||
distribute. Since the parser generator will be available in
|
||||
Python, it should follow that the resulting parser will
|
||||
accessible from Python. Therefore, reference implementations
|
||||
should be available as pure Python code, versus using custom
|
||||
versions of the existing CPython distribution, or as compilable
|
||||
extension modules.
|
||||
|
||||
3. Reference implementations of syntax changes will be easier to
|
||||
discuss with a larger audience. This somewhat falls out of the
|
||||
second assertion, since the community of Python users is most
|
||||
likely larger than the community of CPython developers.
|
||||
3. Reference implementations of syntax changes will be easier to
|
||||
discuss with a larger audience. This somewhat falls out of the
|
||||
second assertion, since the community of Python users is most
|
||||
likely larger than the community of CPython developers.
|
||||
|
||||
4. Development of small languages in Python will be further
|
||||
enhanced, since the additional module will be a fully
|
||||
functional LL(1) parser generator.
|
||||
4. Development of small languages in Python will be further
|
||||
enhanced, since the additional module will be a fully
|
||||
functional LL(1) parser generator.
|
||||
|
||||
|
||||
Specification
|
||||
=============
|
||||
|
||||
The proposed module will be called pgen. The pgen module will
|
||||
contain the following functions:
|
||||
The proposed module will be called ``pgen``. The ``pgen`` module will
|
||||
contain the following functions:
|
||||
|
||||
parseGrammarFile (fileName) -> AST
|
||||
The parseGrammarFile() function will read the file pointed to
|
||||
by fileName and create an AST object. The AST nodes will
|
||||
contain the nonterminal, numeric values of the parser
|
||||
generator meta-grammar. The output AST will be an instance of
|
||||
the AST extension class as provided by the parser module.
|
||||
Syntax errors in the input file will cause the SyntaxError
|
||||
exception to be raised.
|
||||
|
||||
parseGrammarString (text) -> AST
|
||||
The parseGrammarString() function will follow the semantics of
|
||||
the parseGrammarFile(), but accept the grammar text as a
|
||||
string for input, as opposed to the file name.
|
||||
``parseGrammarFile (fileName) -> AST``
|
||||
--------------------------------------
|
||||
|
||||
buildParser (grammarAst) -> DFA
|
||||
The buildParser() function will accept an AST object for input
|
||||
and return a DFA (deterministic finite automaton) data
|
||||
structure. The DFA data structure will be a C extension
|
||||
class, much like the AST structure is provided in the parser
|
||||
module. If the input AST does not conform to the nonterminal
|
||||
codes defined for the pgen meta-grammar, buildParser() will
|
||||
throw a ValueError exception.
|
||||
The ``parseGrammarFile()`` function will read the file pointed to
|
||||
by fileName and create an AST object. The AST nodes will
|
||||
contain the nonterminal, numeric values of the parser
|
||||
generator meta-grammar. The output AST will be an instance of
|
||||
the AST extension class as provided by the parser module.
|
||||
Syntax errors in the input file will cause the SyntaxError
|
||||
exception to be raised.
|
||||
|
||||
parseFile (fileName, dfa, start) -> AST
|
||||
The parseFile() function will essentially be a wrapper for the
|
||||
PyParser_ParseFile() C API function. The wrapper code will
|
||||
accept the DFA C extension class, and the file name. An AST
|
||||
instance that conforms to the lexical values in the token
|
||||
module and the nonterminal values contained in the DFA will be
|
||||
output.
|
||||
|
||||
parseString (text, dfa, start) -> AST
|
||||
The parseString() function will operate in a similar fashion
|
||||
to the parseFile() function, but accept the parse text as an
|
||||
argument. Much like parseFile() will wrap the
|
||||
PyParser_ParseFile() C API function, parseString() will wrap
|
||||
the PyParser_ParseString() function.
|
||||
``parseGrammarString (text) -> AST``
|
||||
------------------------------------
|
||||
|
||||
symbolToStringMap (dfa) -> dict
|
||||
The symbolToStringMap() function will accept a DFA instance
|
||||
and return a dictionary object that maps from the DFA's
|
||||
numeric values for its nonterminals to the string names of the
|
||||
nonterminals as found in the original grammar specification
|
||||
for the DFA.
|
||||
The ``parseGrammarString()`` function will follow the semantics of
|
||||
the ``parseGrammarFile()``, but accept the grammar text as a
|
||||
string for input, as opposed to the file name.
|
||||
|
||||
stringToSymbolMap (dfa) -> dict
|
||||
The stringToSymbolMap() function output a dictionary mapping
|
||||
the nonterminal names of the input DFA to their corresponding
|
||||
numeric values.
|
||||
|
||||
Extra credit will be awarded if the map generation functions and
|
||||
parsing functions are also methods of the DFA extension class.
|
||||
``buildParser (grammarAst) -> DFA``
|
||||
-----------------------------------
|
||||
|
||||
The ``buildParser()`` function will accept an AST object for input
|
||||
and return a DFA (deterministic finite automaton) data
|
||||
structure. The DFA data structure will be a C extension
|
||||
class, much like the AST structure is provided in the parser
|
||||
module. If the input AST does not conform to the nonterminal
|
||||
codes defined for the ``pgen`` meta-grammar, ``buildParser()`` will
|
||||
throw a ``ValueError`` exception.
|
||||
|
||||
|
||||
``parseFile (fileName, dfa, start) -> AST``
|
||||
-------------------------------------------
|
||||
|
||||
The ``parseFile()`` function will essentially be a wrapper for the
|
||||
``PyParser_ParseFile()`` C API function. The wrapper code will
|
||||
accept the DFA C extension class, and the file name. An AST
|
||||
instance that conforms to the lexical values in the token
|
||||
module and the nonterminal values contained in the DFA will be
|
||||
output.
|
||||
|
||||
|
||||
``parseString (text, dfa, start) -> AST``
|
||||
-----------------------------------------
|
||||
|
||||
The ``parseString()`` function will operate in a similar fashion
|
||||
to the ``parseFile()`` function, but accept the parse text as an
|
||||
argument. Much like ``parseFile()`` will wrap the
|
||||
``PyParser_ParseFile()`` C API function, ``parseString()`` will wrap
|
||||
the ``PyParser_ParseString()`` function.
|
||||
|
||||
|
||||
``symbolToStringMap (dfa) -> dict``
|
||||
-----------------------------------
|
||||
|
||||
The ``symbolToStringMap()`` function will accept a DFA instance
|
||||
and return a dictionary object that maps from the DFA's
|
||||
numeric values for its nonterminals to the string names of the
|
||||
nonterminals as found in the original grammar specification
|
||||
for the DFA.
|
||||
|
||||
|
||||
``stringToSymbolMap (dfa) -> dict``
|
||||
-----------------------------------
|
||||
|
||||
The ``stringToSymbolMap()`` function output a dictionary mapping
|
||||
the nonterminal names of the input DFA to their corresponding
|
||||
numeric values.
|
||||
|
||||
|
||||
Extra credit will be awarded if the map generation functions and
|
||||
parsing functions are also methods of the DFA extension class.
|
||||
|
||||
|
||||
Implementation Plan
|
||||
===================
|
||||
|
||||
A cunning plan has been devised to accomplish this enhancement:
|
||||
A cunning plan has been devised to accomplish this enhancement:
|
||||
|
||||
1. Rename the pgen functions to conform to the CPython naming
|
||||
standards. This action may involve adding some header files to
|
||||
the Include subdirectory.
|
||||
1. Rename the ``pgen`` functions to conform to the CPython naming
|
||||
standards. This action may involve adding some header files to
|
||||
the Include subdirectory.
|
||||
|
||||
2. Move the pgen C modules in the Makefile.pre.in from unique pgen
|
||||
elements to the Python C library.
|
||||
2. Move the ``pgen`` C modules in the Makefile.pre.in from unique ``pgen``
|
||||
elements to the Python C library.
|
||||
|
||||
3. Make any needed changes to the parser module so the AST
|
||||
extension class understands that there are AST types it may not
|
||||
understand. Cursory examination of the AST extension class
|
||||
shows that it keeps track of whether the tree is a suite or an
|
||||
expression.
|
||||
3. Make any needed changes to the parser module so the AST
|
||||
extension class understands that there are AST types it may not
|
||||
understand. Cursory examination of the AST extension class
|
||||
shows that it keeps track of whether the tree is a suite or an
|
||||
expression.
|
||||
|
||||
3. Code an additional C module in the Modules directory. The C
|
||||
extension module will implement the DFA extension class and the
|
||||
functions outlined in the previous section.
|
||||
3. Code an additional C module in the Modules directory. The C
|
||||
extension module will implement the DFA extension class and the
|
||||
functions outlined in the previous section.
|
||||
|
||||
4. Add the new module to the build process. Black magic, indeed.
|
||||
4. Add the new module to the build process. Black magic, indeed.
|
||||
|
||||
|
||||
Limitations
|
||||
===========
|
||||
|
||||
Under this proposal, would be designers of Python 3000 will still
|
||||
be constrained to Python's lexical conventions. The addition,
|
||||
subtraction or modification of the Python lexer is outside the
|
||||
scope of this PEP.
|
||||
Under this proposal, would be designers of Python 3000 will still
|
||||
be constrained to Python's lexical conventions. The addition,
|
||||
subtraction or modification of the Python lexer is outside the
|
||||
scope of this PEP.
|
||||
|
||||
|
||||
Reference Implementation
|
||||
========================
|
||||
|
||||
No reference implementation is currently provided. A patch
|
||||
was provided at some point in
|
||||
http://sourceforge.net/tracker/index.php?func=detail&aid=599331&group_id=5470&atid=305470
|
||||
but that patch is no longer maintained.
|
||||
No reference implementation is currently provided. A patch
|
||||
was provided at some point in
|
||||
http://sourceforge.net/tracker/index.php?func=detail&aid=599331&group_id=5470&atid=305470
|
||||
but that patch is no longer maintained.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] The (defunct) Python Compiler-SIG
|
||||
http://www.python.org/sigs/compiler-sig/
|
||||
.. [1] The (defunct) Python Compiler-SIG
|
||||
http://www.python.org/sigs/compiler-sig/
|
||||
|
||||
[2] Parser Module Documentation
|
||||
http://docs.python.org/library/parser.html
|
||||
.. [2] Parser Module Documentation
|
||||
http://docs.python.org/library/parser.html
|
||||
|
||||
[3] Hylton, Jeremy.
|
||||
http://docs.python.org/library/compiler.html
|
||||
.. [3] Hylton, Jeremy.
|
||||
http://docs.python.org/library/compiler.html
|
||||
|
||||
[4] Pelletier, Michel. "Python Interface Syntax", PEP-245.
|
||||
http://www.python.org/dev/peps/pep-0245/
|
||||
.. [4] Pelletier, Michel. "Python Interface Syntax", PEP-245.
|
||||
http://www.python.org/dev/peps/pep-0245/
|
||||
|
||||
[5] The Python Types-SIG
|
||||
http://www.python.org/sigs/types-sig/
|
||||
.. [5] The Python Types-SIG
|
||||
http://www.python.org/sigs/types-sig/
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
fill-column: 70
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
fill-column: 70
|
||||
End:
|
||||
|
|
213
pep-0288.txt
213
pep-0288.txt
|
@ -5,159 +5,168 @@ Last-Modified: $Date$
|
|||
Author: python@rcn.com (Raymond Hettinger)
|
||||
Status: Withdrawn
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 21-Mar-2002
|
||||
Python-Version: 2.5
|
||||
Post-History:
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This PEP proposes to enhance generators by providing mechanisms for
|
||||
raising exceptions and sharing data with running generators.
|
||||
This PEP proposes to enhance generators by providing mechanisms for
|
||||
raising exceptions and sharing data with running generators.
|
||||
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
This PEP is withdrawn. The exception raising mechanism was extended
|
||||
and subsumed into PEP 343. The attribute passing capability
|
||||
never built a following, did not have a clear implementation,
|
||||
and did not have a clean way for the running generator to access
|
||||
its own namespace.
|
||||
This PEP is withdrawn. The exception raising mechanism was extended
|
||||
and subsumed into PEP 343. The attribute passing capability
|
||||
never built a following, did not have a clear implementation,
|
||||
and did not have a clean way for the running generator to access
|
||||
its own namespace.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
Currently, only class based iterators can provide attributes and
|
||||
exception handling. However, class based iterators are harder to
|
||||
write, less compact, less readable, and slower. A better solution
|
||||
is to enable these capabilities for generators.
|
||||
Currently, only class based iterators can provide attributes and
|
||||
exception handling. However, class based iterators are harder to
|
||||
write, less compact, less readable, and slower. A better solution
|
||||
is to enable these capabilities for generators.
|
||||
|
||||
Enabling attribute assignments allows data to be passed to and from
|
||||
running generators. The approach of sharing data using attributes
|
||||
pervades Python. Other approaches exist but are somewhat hackish
|
||||
in comparison.
|
||||
Enabling attribute assignments allows data to be passed to and from
|
||||
running generators. The approach of sharing data using attributes
|
||||
pervades Python. Other approaches exist but are somewhat hackish
|
||||
in comparison.
|
||||
|
||||
Another evolutionary step is to add a generator method to allow
|
||||
exceptions to be passed to a generator. Currently, there is no
|
||||
clean method for triggering exceptions from outside the generator.
|
||||
Also, generator exception passing helps mitigate the try/finally
|
||||
prohibition for generators. The need is especially acute for
|
||||
generators needing to flush buffers or close resources upon termination.
|
||||
|
||||
The two proposals are backwards compatible and require no new
|
||||
keywords. They are being recommended for Python version 2.5.
|
||||
Another evolutionary step is to add a generator method to allow
|
||||
exceptions to be passed to a generator. Currently, there is no
|
||||
clean method for triggering exceptions from outside the generator.
|
||||
Also, generator exception passing helps mitigate the try/finally
|
||||
prohibition for generators. The need is especially acute for
|
||||
generators needing to flush buffers or close resources upon termination.
|
||||
|
||||
The two proposals are backwards compatible and require no new
|
||||
keywords. They are being recommended for Python version 2.5.
|
||||
|
||||
|
||||
|
||||
Specification for Generator Attributes
|
||||
======================================
|
||||
|
||||
Essentially, the proposal is to emulate attribute writing for classes.
|
||||
The only wrinkle is that generators lack a way to refer to instances of
|
||||
themselves. So, the proposal is to provide a function for discovering
|
||||
the reference. For example:
|
||||
Essentially, the proposal is to emulate attribute writing for classes.
|
||||
The only wrinkle is that generators lack a way to refer to instances of
|
||||
themselves. So, the proposal is to provide a function for discovering
|
||||
the reference. For example::
|
||||
|
||||
def mygen(filename):
|
||||
self = sys.get_generator()
|
||||
myfile = open(filename)
|
||||
for line in myfile:
|
||||
if len(line) < 10:
|
||||
continue
|
||||
self.pos = myfile.tell()
|
||||
yield line.upper()
|
||||
def mygen(filename):
|
||||
self = sys.get_generator()
|
||||
myfile = open(filename)
|
||||
for line in myfile:
|
||||
if len(line) < 10:
|
||||
continue
|
||||
self.pos = myfile.tell()
|
||||
yield line.upper()
|
||||
|
||||
g = mygen('sample.txt')
|
||||
line1 = g.next()
|
||||
print 'Position', g.pos
|
||||
g = mygen('sample.txt')
|
||||
line1 = g.next()
|
||||
print 'Position', g.pos
|
||||
|
||||
Uses for generator attributes include:
|
||||
Uses for generator attributes include:
|
||||
|
||||
1. Providing generator clients with extra information (as shown
|
||||
above).
|
||||
2. Externally setting control flags governing generator operation
|
||||
(possibly telling a generator when to step in or step over
|
||||
data groups).
|
||||
3. Writing lazy consumers with complex execution states
|
||||
(an arithmetic encoder output stream for example).
|
||||
4. Writing co-routines (as demonstrated in Dr. Mertz's articles [1]).
|
||||
1. Providing generator clients with extra information (as shown
|
||||
above).
|
||||
2. Externally setting control flags governing generator operation
|
||||
(possibly telling a generator when to step in or step over
|
||||
data groups).
|
||||
3. Writing lazy consumers with complex execution states
|
||||
(an arithmetic encoder output stream for example).
|
||||
4. Writing co-routines (as demonstrated in Dr. Mertz's articles [1]_).
|
||||
|
||||
The control flow of 'yield' and 'next' is unchanged by this
|
||||
proposal. The only change is that data can passed to and from the
|
||||
generator. Most of the underlying machinery is already in place,
|
||||
only the access function needs to be added.
|
||||
The control flow of 'yield' and 'next' is unchanged by this
|
||||
proposal. The only change is that data can passed to and from the
|
||||
generator. Most of the underlying machinery is already in place,
|
||||
only the access function needs to be added.
|
||||
|
||||
|
||||
|
||||
Specification for Generator Exception Passing:
|
||||
Specification for Generator Exception Passing
|
||||
=============================================
|
||||
|
||||
Add a .throw(exception) method to the generator interface:
|
||||
Add a .throw(exception) method to the generator interface::
|
||||
|
||||
def logger():
|
||||
start = time.time()
|
||||
log = []
|
||||
try:
|
||||
while True:
|
||||
log.append(time.time() - start)
|
||||
yield log[-1]
|
||||
except WriteLog:
|
||||
writelog(log)
|
||||
def logger():
|
||||
start = time.time()
|
||||
log = []
|
||||
try:
|
||||
while True:
|
||||
log.append(time.time() - start)
|
||||
yield log[-1]
|
||||
except WriteLog:
|
||||
writelog(log)
|
||||
|
||||
g = logger()
|
||||
for i in [10,20,40,80,160]:
|
||||
testsuite(i)
|
||||
g.next()
|
||||
g.throw(WriteLog)
|
||||
g = logger()
|
||||
for i in [10,20,40,80,160]:
|
||||
testsuite(i)
|
||||
g.next()
|
||||
g.throw(WriteLog)
|
||||
|
||||
There is no existing work-around for triggering an exception
|
||||
inside a generator. It is the only case in Python where active
|
||||
code cannot be excepted to or through.
|
||||
There is no existing work-around for triggering an exception
|
||||
inside a generator. It is the only case in Python where active
|
||||
code cannot be excepted to or through.
|
||||
|
||||
Generator exception passing also helps address an intrinsic
|
||||
limitation on generators, the prohibition against their using
|
||||
try/finally to trigger clean-up code [2].
|
||||
Generator exception passing also helps address an intrinsic
|
||||
limitation on generators, the prohibition against their using
|
||||
try/finally to trigger clean-up code [2]_.
|
||||
|
||||
Note A: The name of the throw method was selected for several
|
||||
reasons. Raise is a keyword and so cannot be used as a method
|
||||
name. Unlike raise which immediately raises an exception from the
|
||||
current execution point, throw will first return to the generator
|
||||
and then raise the exception. The word throw is suggestive of
|
||||
putting the exception in another location. The word throw is
|
||||
already associated with exceptions in other languages.
|
||||
Note A: The name of the throw method was selected for several
|
||||
reasons. Raise is a keyword and so cannot be used as a method
|
||||
name. Unlike raise which immediately raises an exception from the
|
||||
current execution point, throw will first return to the generator
|
||||
and then raise the exception. The word throw is suggestive of
|
||||
putting the exception in another location. The word throw is
|
||||
already associated with exceptions in other languages.
|
||||
|
||||
Alternative method names were considered: resolve(), signal(),
|
||||
genraise(), raiseinto(), and flush(). None of these fit as well
|
||||
as throw().
|
||||
Alternative method names were considered: ``resolve()``, ``signal()``,
|
||||
``genraise()``, ``raiseinto()``, and ``flush()``. None of these fit as well
|
||||
as ``throw()``.
|
||||
|
||||
Note B: To keep the throw() syntax simple only the instance
|
||||
version of the raise syntax would be supported (no variants for
|
||||
"raise string" or "raise class, instance").
|
||||
Note B: To keep the ``throw()`` syntax simple only the instance
|
||||
version of the raise syntax would be supported (no variants for
|
||||
"raise string" or "raise class, instance").
|
||||
|
||||
Calling "g.throw(instance)" would correspond to writing
|
||||
"raise instance" immediately after the most recent yield.
|
||||
Calling ``g.throw(instance)`` would correspond to writing
|
||||
``raise instance`` immediately after the most recent yield.
|
||||
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] Dr. David Mertz's draft columns for Charming Python:
|
||||
http://gnosis.cx/publish/programming/charming_python_b5.txt
|
||||
http://gnosis.cx/publish/programming/charming_python_b7.txt
|
||||
.. [1] Dr. David Mertz's draft columns for Charming Python
|
||||
http://gnosis.cx/publish/programming/charming_python_b5.txt
|
||||
http://gnosis.cx/publish/programming/charming_python_b7.txt
|
||||
|
||||
[2] PEP 255 Simple Generators:
|
||||
http://www.python.org/dev/peps/pep-0255/
|
||||
.. [2] PEP 255 Simple Generators
|
||||
http://www.python.org/dev/peps/pep-0255/
|
||||
|
||||
[3] Proof-of-concept recipe:
|
||||
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/164044
|
||||
.. [3] Proof-of-concept recipe
|
||||
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/164044
|
||||
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
fill-column: 70
|
||||
End:
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
fill-column: 70
|
||||
End:
|
||||
|
|
240
pep-0312.txt
240
pep-0312.txt
|
@ -5,181 +5,205 @@ Last-Modified: $Date$
|
|||
Author: Roman Suzi <rnd@onego.ru>, Alex Martelli <aleaxit@gmail.com>
|
||||
Status: Deferred
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 11-Feb-2003
|
||||
Python-Version: 2.4
|
||||
Post-History:
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This PEP proposes to make argumentless lambda keyword optional in
|
||||
some cases where it is not grammatically ambiguous.
|
||||
|
||||
This PEP proposes to make argumentless lambda keyword optional in
|
||||
some cases where it is not grammatically ambiguous.
|
||||
|
||||
Deferral
|
||||
========
|
||||
|
||||
The BDFL hates the unary colon syntax. This PEP needs to go back
|
||||
to the drawing board and find a more Pythonic syntax (perhaps an
|
||||
alternative unary operator). See python-dev discussion on
|
||||
17 June 2005.
|
||||
The BDFL hates the unary colon syntax. This PEP needs to go back
|
||||
to the drawing board and find a more Pythonic syntax (perhaps an
|
||||
alternative unary operator). See python-dev discussion on
|
||||
17 June 2005 [1]_.
|
||||
|
||||
Also, it is probably a good idea to eliminate the alternative
|
||||
propositions which have no chance at all. The examples section
|
||||
is good and highlights the readability improvements. It would
|
||||
carry more weight with additional examples and with real-world
|
||||
referents (instead of the abstracted dummy calls to ``:A`` and ``:B``).
|
||||
|
||||
Also, it is probably a good idea to eliminate the alternative
|
||||
propositions which have no chance at all. The examples section
|
||||
is good and highlights the readability improvements. It would
|
||||
carry more weight with additional examples and with real-world
|
||||
referents (instead of the abstracted dummy calls to :A and :B).
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
Lambdas are useful for defining anonymous functions, e.g. for use
|
||||
as callbacks or (pseudo)-lazy evaluation schemes. Often, lambdas
|
||||
are not used when they would be appropriate, just because the
|
||||
keyword "lambda" makes code look complex. Omitting lambda in some
|
||||
special cases is possible, with small and backwards compatible
|
||||
changes to the grammar, and provides a cheap cure against such
|
||||
"lambdaphobia".
|
||||
Lambdas are useful for defining anonymous functions, e.g. for use
|
||||
as callbacks or (pseudo)-lazy evaluation schemes. Often, lambdas
|
||||
are not used when they would be appropriate, just because the
|
||||
keyword "lambda" makes code look complex. Omitting lambda in some
|
||||
special cases is possible, with small and backwards compatible
|
||||
changes to the grammar, and provides a cheap cure against such
|
||||
"lambdaphobia".
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
Sometimes people do not use lambdas because they fear to introduce
|
||||
a term with a theory behind it. This proposal makes introducing
|
||||
argumentless lambdas easier, by omitting the "lambda" keyword.
|
||||
itself. Implementation can be done simply changing grammar so it
|
||||
lets the "lambda" keyword be implied in a few well-known cases.
|
||||
In particular, adding surrounding brackets lets you specify
|
||||
nullary lambda anywhere.
|
||||
Sometimes people do not use lambdas because they fear to introduce
|
||||
a term with a theory behind it. This proposal makes introducing
|
||||
argumentless lambdas easier, by omitting the "lambda" keyword.
|
||||
itself. Implementation can be done simply changing grammar so it
|
||||
lets the "lambda" keyword be implied in a few well-known cases.
|
||||
In particular, adding surrounding brackets lets you specify
|
||||
nullary lambda anywhere.
|
||||
|
||||
|
||||
Syntax
|
||||
======
|
||||
|
||||
An argumentless "lambda" keyword can be omitted in the following
|
||||
cases:
|
||||
An argumentless "lambda" keyword can be omitted in the following
|
||||
cases:
|
||||
|
||||
* immediately after "=" in named parameter assignment or default
|
||||
value assignment;
|
||||
* immediately after "=" in named parameter assignment or default
|
||||
value assignment;
|
||||
|
||||
* immediately after "(" in any expression;
|
||||
* immediately after "(" in any expression;
|
||||
|
||||
* immediately after a "," in a function argument list;
|
||||
* immediately after a "," in a function argument list;
|
||||
|
||||
* immediately after a ":" in a dictionary literal; (not
|
||||
implemented)
|
||||
* immediately after a ":" in a dictionary literal; (not
|
||||
implemented)
|
||||
|
||||
* in an assignment statement; (not implemented)
|
||||
* in an assignment statement; (not implemented)
|
||||
|
||||
|
||||
Examples of Use
|
||||
===============
|
||||
|
||||
1) Inline "if":
|
||||
1) Inline ``if``::
|
||||
|
||||
def ifelse(cond, true_part, false_part):
|
||||
if cond:
|
||||
return true_part()
|
||||
else:
|
||||
return false_part()
|
||||
def ifelse(cond, true_part, false_part):
|
||||
if cond:
|
||||
return true_part()
|
||||
else:
|
||||
return false_part()
|
||||
|
||||
# old syntax:
|
||||
print ifelse(a < b, lambda:A, lambda:B)
|
||||
# old syntax:
|
||||
print ifelse(a < b, lambda:A, lambda:B)
|
||||
|
||||
# new syntax:
|
||||
print ifelse(a < b, :A, :B)
|
||||
# new syntax:
|
||||
print ifelse(a < b, :A, :B)
|
||||
|
||||
# parts A and B may require extensive processing, as in:
|
||||
print ifelse(a < b, :ext_proc1(A), :ext_proc2(B))
|
||||
# parts A and B may require extensive processing, as in:
|
||||
print ifelse(a < b, :ext_proc1(A), :ext_proc2(B))
|
||||
|
||||
2) Locking:
|
||||
2) Locking::
|
||||
|
||||
def with(alock, acallable):
|
||||
alock.acquire()
|
||||
try:
|
||||
acallable()
|
||||
finally:
|
||||
alock.release()
|
||||
def with(alock, acallable):
|
||||
alock.acquire()
|
||||
try:
|
||||
acallable()
|
||||
finally:
|
||||
alock.release()
|
||||
|
||||
with(mylock, :x(y(), 23, z(), 'foo'))
|
||||
with(mylock, :x(y(), 23, z(), 'foo'))
|
||||
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Implementation requires some tweaking of the Grammar/Grammar file
|
||||
in the Python sources, and some adjustment of
|
||||
Modules/parsermodule.c to make syntactic and pragmatic changes.
|
||||
Implementation requires some tweaking of the Grammar/Grammar file
|
||||
in the Python sources, and some adjustment of
|
||||
Modules/parsermodule.c to make syntactic and pragmatic changes.
|
||||
|
||||
(Some grammar/parser guru is needed to make a full
|
||||
implementation.)
|
||||
(Some grammar/parser guru is needed to make a full
|
||||
implementation.)
|
||||
|
||||
Here are the changes needed to Grammar to allow implicit lambda:
|
||||
Here are the changes needed to Grammar to allow implicit lambda::
|
||||
|
||||
varargslist: (fpdef ['=' imptest] ',')* ('*' NAME [',' '**'
|
||||
NAME] | '**' NAME) | fpdef ['=' imptest] (',' fpdef ['='
|
||||
imptest])* [',']
|
||||
varargslist: (fpdef ['=' imptest] ',')* ('*' NAME [',' '**'
|
||||
NAME] | '**' NAME) | fpdef ['=' imptest] (',' fpdef ['='
|
||||
imptest])* [',']
|
||||
|
||||
imptest: test | implambdef
|
||||
imptest: test | implambdef
|
||||
|
||||
atom: '(' [imptestlist] ')' | '[' [listmaker] ']' |
|
||||
'{' [dictmaker] '}' | '`' testlist1 '`' | NAME | NUMBER | STRING+
|
||||
atom: '(' [imptestlist] ')' | '[' [listmaker] ']' |
|
||||
'{' [dictmaker] '}' | '`' testlist1 '`' | NAME | NUMBER | STRING+
|
||||
|
||||
implambdef: ':' test
|
||||
implambdef: ':' test
|
||||
|
||||
imptestlist: imptest (',' imptest)* [',']
|
||||
imptestlist: imptest (',' imptest)* [',']
|
||||
|
||||
argument: [test '='] imptest
|
||||
argument: [test '='] imptest
|
||||
|
||||
Three new non-terminals are needed: imptest for the place where
|
||||
implicit lambda may occur, implambdef for the implicit lambda
|
||||
definition itself, imptestlist for a place where imptest's may
|
||||
occur.
|
||||
Three new non-terminals are needed: ``imptest`` for the place where
|
||||
implicit lambda may occur, ``implambdef`` for the implicit lambda
|
||||
definition itself, ``imptestlist`` for a place where ``imptest``'s may
|
||||
occur.
|
||||
|
||||
This implementation is not complete. First, because some files in
|
||||
Parser module need to be updated. Second, some additional places
|
||||
aren't implemented, see Syntax section above.
|
||||
This implementation is not complete. First, because some files in
|
||||
Parser module need to be updated. Second, some additional places
|
||||
aren't implemented, see Syntax section above.
|
||||
|
||||
|
||||
Discussion
|
||||
==========
|
||||
|
||||
This feature is not a high-visibility one (the only novel part is
|
||||
the absence of lambda). The feature is intended to make null-ary
|
||||
lambdas more appealing syntactically, to provide lazy evaluation
|
||||
of expressions in some simple cases. This proposal is not targeted
|
||||
at more advanced cases (demanding arguments for the lambda).
|
||||
This feature is not a high-visibility one (the only novel part is
|
||||
the absence of lambda). The feature is intended to make null-ary
|
||||
lambdas more appealing syntactically, to provide lazy evaluation
|
||||
of expressions in some simple cases. This proposal is not targeted
|
||||
at more advanced cases (demanding arguments for the lambda).
|
||||
|
||||
There is an alternative proposition for implicit lambda: implicit
|
||||
lambda with unused arguments. In this case the function defined by
|
||||
such lambda can accept any parameters, i.e. be equivalent to:
|
||||
lambda *args: expr. This form would be more powerful. Grep in the
|
||||
standard library revealed that such lambdas are indeed in use.
|
||||
There is an alternative proposition for implicit lambda: implicit
|
||||
lambda with unused arguments. In this case the function defined by
|
||||
such lambda can accept any parameters, i.e. be equivalent to:
|
||||
``lambda *args: expr``. This form would be more powerful. Grep in the
|
||||
standard library revealed that such lambdas are indeed in use.
|
||||
|
||||
One more extension can provide a way to have a list of parameters
|
||||
passed to a function defined by implicit lambda. However, such
|
||||
parameters need some special name to be accessed and are unlikely
|
||||
to be included in the language. Possible local names for such
|
||||
parameters are: _, __args__, __. For example:
|
||||
One more extension can provide a way to have a list of parameters
|
||||
passed to a function defined by implicit lambda. However, such
|
||||
parameters need some special name to be accessed and are unlikely
|
||||
to be included in the language. Possible local names for such
|
||||
parameters are: ``_``, ``__args__``, ``__``. For example::
|
||||
|
||||
reduce(:_[0] + _[1], [1,2,3], 0)
|
||||
reduce(:__[0] + __[1], [1,2,3], 0)
|
||||
reduce(:__args__[0] + __args__[1], [1,2,3], 0)
|
||||
reduce(:_[0] + _[1], [1,2,3], 0)
|
||||
reduce(:__[0] + __[1], [1,2,3], 0)
|
||||
reduce(:__args__[0] + __args__[1], [1,2,3], 0)
|
||||
|
||||
These forms do not look very nice, and in the PEP author's opinion
|
||||
do not justify the removal of the lambda keyword in such cases.
|
||||
These forms do not look very nice, and in the PEP author's opinion
|
||||
do not justify the removal of the lambda keyword in such cases.
|
||||
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
The idea of dropping lambda was first coined by Paul Rubin at 08
|
||||
Feb 2003 16:39:30 -0800 in comp.lang.python while discussing the
|
||||
thread "For review: PEP 308 - If-then-else expression" [2]_.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Guido van Rossum, Recommend accepting PEP 312 -- Simple Implicit Lambda
|
||||
https://mail.python.org/pipermail/python-dev/2005-June/054304.html
|
||||
|
||||
.. [2] Guido van Rossum, For review: PEP 308 - If-then-else expression
|
||||
https://mail.python.org/pipermail/python-dev/2003-February/033178.html
|
||||
|
||||
The idea of dropping lambda was first coined by Paul Rubin at 08
|
||||
Feb 2003 16:39:30 -0800 in comp.lang.python while discussing the
|
||||
thread "For review: PEP 308 - If-then-else expression".
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
End:
|
||||
|
|
202
pep-0315.txt
202
pep-0315.txt
|
@ -2,160 +2,176 @@ PEP: 315
|
|||
Title: Enhanced While Loop
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: Raymond Hettinger <python@rcn.com>
|
||||
W Isaac Carroll <icarroll@pobox.com>
|
||||
Author: Raymond Hettinger <python@rcn.com>, W Isaac Carroll <icarroll@pobox.com>
|
||||
Status: Rejected
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 25-Apr-2003
|
||||
Python-Version: 2.5
|
||||
Post-History:
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This PEP proposes adding an optional "do" clause to the beginning
|
||||
of the while loop to make loop code clearer and reduce errors
|
||||
caused by code duplication.
|
||||
This PEP proposes adding an optional "do" clause to the beginning
|
||||
of the while loop to make loop code clearer and reduce errors
|
||||
caused by code duplication.
|
||||
|
||||
|
||||
Notice
|
||||
======
|
||||
|
||||
Rejected; see
|
||||
http://mail.python.org/pipermail/python-ideas/2013-June/021610.html
|
||||
Rejected; see [1]_.
|
||||
|
||||
This PEP has been deferred since 2006; see
|
||||
http://mail.python.org/pipermail/python-dev/2006-February/060718.html
|
||||
This PEP has been deferred since 2006; see [2]_.
|
||||
|
||||
Subsequent efforts to revive the PEP in April 2009 did not
|
||||
meet with success because no syntax emerged that could
|
||||
compete with the following form:
|
||||
Subsequent efforts to revive the PEP in April 2009 did not
|
||||
meet with success because no syntax emerged that could
|
||||
compete with the following form::
|
||||
|
||||
while True:
|
||||
<setup code>
|
||||
if not <condition>:
|
||||
break
|
||||
<loop body>
|
||||
while True:
|
||||
<setup code>
|
||||
if not <condition>:
|
||||
break
|
||||
<loop body>
|
||||
|
||||
A syntax alternative to the one proposed in the PEP was found for
|
||||
a basic do-while loop but it gained little support because the
|
||||
condition was at the top:
|
||||
A syntax alternative to the one proposed in the PEP was found for
|
||||
a basic do-while loop but it gained little support because the
|
||||
condition was at the top::
|
||||
|
||||
do ... while <cond>:
|
||||
<loop body>
|
||||
do ... while <cond>:
|
||||
<loop body>
|
||||
|
||||
Users of the language are advised to use the while-True form with
|
||||
an inner if-break when a do-while loop would have been appropriate.
|
||||
Users of the language are advised to use the while-True form with
|
||||
an inner if-break when a do-while loop would have been appropriate.
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
It is often necessary for some code to be executed before each
|
||||
evaluation of the while loop condition. This code is often
|
||||
duplicated outside the loop, as setup code that executes once
|
||||
before entering the loop:
|
||||
It is often necessary for some code to be executed before each
|
||||
evaluation of the while loop condition. This code is often
|
||||
duplicated outside the loop, as setup code that executes once
|
||||
before entering the loop::
|
||||
|
||||
<setup code>
|
||||
while <condition>:
|
||||
<loop body>
|
||||
<setup code>
|
||||
while <condition>:
|
||||
<loop body>
|
||||
<setup code>
|
||||
|
||||
The problem is that duplicated code can be a source of errors if
|
||||
one instance is changed but the other is not. Also, the purpose
|
||||
of the second instance of the setup code is not clear because it
|
||||
comes at the end of the loop.
|
||||
The problem is that duplicated code can be a source of errors if
|
||||
one instance is changed but the other is not. Also, the purpose
|
||||
of the second instance of the setup code is not clear because it
|
||||
comes at the end of the loop.
|
||||
|
||||
It is possible to prevent code duplication by moving the loop
|
||||
condition into a helper function, or an if statement in the loop
|
||||
body. However, separating the loop condition from the while
|
||||
keyword makes the behavior of the loop less clear:
|
||||
It is possible to prevent code duplication by moving the loop
|
||||
condition into a helper function, or an if statement in the loop
|
||||
body. However, separating the loop condition from the while
|
||||
keyword makes the behavior of the loop less clear::
|
||||
|
||||
def helper(args):
|
||||
<setup code>
|
||||
return <condition>
|
||||
def helper(args):
|
||||
<setup code>
|
||||
return <condition>
|
||||
|
||||
while helper(args):
|
||||
<loop body>
|
||||
while helper(args):
|
||||
<loop body>
|
||||
|
||||
This last form has the additional drawback of requiring the loop's
|
||||
else clause to be added to the body of the if statement, further
|
||||
obscuring the loop's behavior:
|
||||
This last form has the additional drawback of requiring the loop's
|
||||
else clause to be added to the body of the if statement, further
|
||||
obscuring the loop's behavior::
|
||||
|
||||
while True:
|
||||
<setup code>
|
||||
if not <condition>: break
|
||||
<loop body>
|
||||
while True:
|
||||
<setup code>
|
||||
if not <condition>: break
|
||||
<loop body>
|
||||
|
||||
This PEP proposes to solve these problems by adding an optional
|
||||
clause to the while loop, which allows the setup code to be
|
||||
expressed in a natural way:
|
||||
This PEP proposes to solve these problems by adding an optional
|
||||
clause to the while loop, which allows the setup code to be
|
||||
expressed in a natural way::
|
||||
|
||||
do:
|
||||
<setup code>
|
||||
while <condition>:
|
||||
<loop body>
|
||||
do:
|
||||
<setup code>
|
||||
while <condition>:
|
||||
<loop body>
|
||||
|
||||
This keeps the loop condition with the while keyword where it
|
||||
belongs, and does not require code to be duplicated.
|
||||
This keeps the loop condition with the while keyword where it
|
||||
belongs, and does not require code to be duplicated.
|
||||
|
||||
|
||||
Syntax
|
||||
======
|
||||
|
||||
The syntax of the while statement
|
||||
The syntax of the while statement::
|
||||
|
||||
while_stmt : "while" expression ":" suite
|
||||
["else" ":" suite]
|
||||
while_stmt : "while" expression ":" suite
|
||||
["else" ":" suite]
|
||||
|
||||
is extended as follows:
|
||||
is extended as follows::
|
||||
|
||||
while_stmt : ["do" ":" suite]
|
||||
"while" expression ":" suite
|
||||
["else" ":" suite]
|
||||
while_stmt : ["do" ":" suite]
|
||||
"while" expression ":" suite
|
||||
["else" ":" suite]
|
||||
|
||||
|
||||
Semantics of break and continue
|
||||
===============================
|
||||
|
||||
In the do-while loop the break statement will behave the same as
|
||||
in the standard while loop: It will immediately terminate the loop
|
||||
without evaluating the loop condition or executing the else
|
||||
clause.
|
||||
In the do-while loop the break statement will behave the same as
|
||||
in the standard while loop: It will immediately terminate the loop
|
||||
without evaluating the loop condition or executing the else
|
||||
clause.
|
||||
|
||||
A continue statement in the do-while loop jumps to the while
|
||||
condition check.
|
||||
A continue statement in the do-while loop jumps to the while
|
||||
condition check.
|
||||
|
||||
In general, when the while suite is empty (a pass statement),
|
||||
the do-while loop and break and continue statements should match
|
||||
the semantics of do-while in other languages.
|
||||
In general, when the while suite is empty (a pass statement),
|
||||
the do-while loop and break and continue statements should match
|
||||
the semantics of do-while in other languages.
|
||||
|
||||
Likewise, when the do suite is empty, the do-while loop and
|
||||
break and continue statements should match behavior found
|
||||
in regular while loops.
|
||||
Likewise, when the do suite is empty, the do-while loop and
|
||||
break and continue statements should match behavior found
|
||||
in regular while loops.
|
||||
|
||||
|
||||
Future Statement
|
||||
================
|
||||
|
||||
Because of the new keyword "do", the statement
|
||||
Because of the new keyword "do", the statement::
|
||||
|
||||
from __future__ import do_while
|
||||
from __future__ import do_while
|
||||
|
||||
will initially be required to use the do-while form.
|
||||
will initially be required to use the do-while form.
|
||||
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
The first implementation of this PEP can compile the do-while loop
|
||||
as an infinite loop with a test that exits the loop.
|
||||
The first implementation of this PEP can compile the do-while loop
|
||||
as an infinite loop with a test that exits the loop.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Guido van Rossum, PEP 315: do-while
|
||||
https://mail.python.org/pipermail/python-ideas/2013-June/021610.html
|
||||
|
||||
.. [2] Raymond Hettinger, release plan for 2.5 ?
|
||||
https://mail.python.org/pipermail/python-dev/2006-February/060718.html
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document is placed in the public domain.
|
||||
This document is placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 75
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 75
|
||||
End:
|
||||
|
|
255
pep-3102.txt
255
pep-3102.txt
|
@ -5,186 +5,193 @@ Last-Modified: $Date$
|
|||
Author: Talin <viridia@gmail.com>
|
||||
Status: Final
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 22-Apr-2006
|
||||
Python-Version: 3.0
|
||||
Post-History: 28-Apr-2006, May-19-2006
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This PEP proposes a change to the way that function arguments are
|
||||
assigned to named parameter slots. In particular, it enables the
|
||||
declaration of "keyword-only" arguments: arguments that can only
|
||||
be supplied by keyword and which will never be automatically
|
||||
filled in by a positional argument.
|
||||
This PEP proposes a change to the way that function arguments are
|
||||
assigned to named parameter slots. In particular, it enables the
|
||||
declaration of "keyword-only" arguments: arguments that can only
|
||||
be supplied by keyword and which will never be automatically
|
||||
filled in by a positional argument.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
The current Python function-calling paradigm allows arguments to
|
||||
be specified either by position or by keyword. An argument can be
|
||||
filled in either explicitly by name, or implicitly by position.
|
||||
The current Python function-calling paradigm allows arguments to
|
||||
be specified either by position or by keyword. An argument can be
|
||||
filled in either explicitly by name, or implicitly by position.
|
||||
|
||||
There are often cases where it is desirable for a function to take
|
||||
a variable number of arguments. The Python language supports this
|
||||
using the 'varargs' syntax ('*name'), which specifies that any
|
||||
'left over' arguments be passed into the varargs parameter as a
|
||||
tuple.
|
||||
There are often cases where it is desirable for a function to take
|
||||
a variable number of arguments. The Python language supports this
|
||||
using the 'varargs' syntax (``*name``), which specifies that any
|
||||
'left over' arguments be passed into the varargs parameter as a
|
||||
tuple.
|
||||
|
||||
One limitation on this is that currently, all of the regular
|
||||
argument slots must be filled before the vararg slot can be.
|
||||
One limitation on this is that currently, all of the regular
|
||||
argument slots must be filled before the vararg slot can be.
|
||||
|
||||
This is not always desirable. One can easily envision a function
|
||||
which takes a variable number of arguments, but also takes one
|
||||
or more 'options' in the form of keyword arguments. Currently,
|
||||
the only way to do this is to define both a varargs argument,
|
||||
and a 'keywords' argument (**kwargs), and then manually extract
|
||||
the desired keywords from the dictionary.
|
||||
This is not always desirable. One can easily envision a function
|
||||
which takes a variable number of arguments, but also takes one
|
||||
or more 'options' in the form of keyword arguments. Currently,
|
||||
the only way to do this is to define both a varargs argument,
|
||||
and a 'keywords' argument (``**kwargs``), and then manually extract
|
||||
the desired keywords from the dictionary.
|
||||
|
||||
|
||||
Specification
|
||||
=============
|
||||
|
||||
Syntactically, the proposed changes are fairly simple. The first
|
||||
change is to allow regular arguments to appear after a varargs
|
||||
argument:
|
||||
Syntactically, the proposed changes are fairly simple. The first
|
||||
change is to allow regular arguments to appear after a varargs
|
||||
argument::
|
||||
|
||||
def sortwords(*wordlist, case_sensitive=False):
|
||||
...
|
||||
def sortwords(*wordlist, case_sensitive=False):
|
||||
...
|
||||
|
||||
This function accepts any number of positional arguments, and it
|
||||
also accepts a keyword option called 'case_sensitive'. This
|
||||
option will never be filled in by a positional argument, but
|
||||
must be explicitly specified by name.
|
||||
This function accepts any number of positional arguments, and it
|
||||
also accepts a keyword option called 'case_sensitive'. This
|
||||
option will never be filled in by a positional argument, but
|
||||
must be explicitly specified by name.
|
||||
|
||||
Keyword-only arguments are not required to have a default value.
|
||||
Since Python requires that all arguments be bound to a value,
|
||||
and since the only way to bind a value to a keyword-only argument
|
||||
is via keyword, such arguments are therefore 'required keyword'
|
||||
arguments. Such arguments must be supplied by the caller, and
|
||||
they must be supplied via keyword.
|
||||
Keyword-only arguments are not required to have a default value.
|
||||
Since Python requires that all arguments be bound to a value,
|
||||
and since the only way to bind a value to a keyword-only argument
|
||||
is via keyword, such arguments are therefore 'required keyword'
|
||||
arguments. Such arguments must be supplied by the caller, and
|
||||
they must be supplied via keyword.
|
||||
|
||||
The second syntactical change is to allow the argument name to
|
||||
be omitted for a varargs argument. The meaning of this is to
|
||||
allow for keyword-only arguments for functions that would not
|
||||
otherwise take a varargs argument:
|
||||
The second syntactical change is to allow the argument name to
|
||||
be omitted for a varargs argument. The meaning of this is to
|
||||
allow for keyword-only arguments for functions that would not
|
||||
otherwise take a varargs argument::
|
||||
|
||||
def compare(a, b, *, key=None):
|
||||
...
|
||||
def compare(a, b, *, key=None):
|
||||
...
|
||||
|
||||
The reasoning behind this change is as follows. Imagine for a
|
||||
moment a function which takes several positional arguments, as
|
||||
well as a keyword argument:
|
||||
The reasoning behind this change is as follows. Imagine for a
|
||||
moment a function which takes several positional arguments, as
|
||||
well as a keyword argument::
|
||||
|
||||
def compare(a, b, key=None):
|
||||
...
|
||||
def compare(a, b, key=None):
|
||||
...
|
||||
|
||||
Now, suppose you wanted to have 'key' be a keyword-only argument.
|
||||
Under the above syntax, you could accomplish this by adding a
|
||||
varargs argument immediately before the keyword argument:
|
||||
Now, suppose you wanted to have 'key' be a keyword-only argument.
|
||||
Under the above syntax, you could accomplish this by adding a
|
||||
varargs argument immediately before the keyword argument::
|
||||
|
||||
def compare(a, b, *ignore, key=None):
|
||||
...
|
||||
def compare(a, b, *ignore, key=None):
|
||||
...
|
||||
|
||||
Unfortunately, the 'ignore' argument will also suck up any
|
||||
erroneous positional arguments that may have been supplied by the
|
||||
caller. Given that we'd prefer any unwanted arguments to raise an
|
||||
error, we could do this:
|
||||
Unfortunately, the 'ignore' argument will also suck up any
|
||||
erroneous positional arguments that may have been supplied by the
|
||||
caller. Given that we'd prefer any unwanted arguments to raise an
|
||||
error, we could do this::
|
||||
|
||||
def compare(a, b, *ignore, key=None):
|
||||
if ignore: # If ignore is not empty
|
||||
raise TypeError
|
||||
def compare(a, b, *ignore, key=None):
|
||||
if ignore: # If ignore is not empty
|
||||
raise TypeError
|
||||
|
||||
As a convenient shortcut, we can simply omit the 'ignore' name,
|
||||
meaning 'don't allow any positional arguments beyond this point'.
|
||||
|
||||
(Note: After much discussion of alternative syntax proposals, the
|
||||
BDFL has pronounced in favor of this 'single star' syntax for
|
||||
indicating the end of positional parameters.)
|
||||
|
||||
As a convenient shortcut, we can simply omit the 'ignore' name,
|
||||
meaning 'don't allow any positional arguments beyond this point'.
|
||||
|
||||
(Note: After much discussion of alternative syntax proposals, the
|
||||
BDFL has pronounced in favor of this 'single star' syntax for
|
||||
indicating the end of positional parameters.)
|
||||
|
||||
|
||||
Function Calling Behavior
|
||||
=========================
|
||||
|
||||
The previous section describes the difference between the old
|
||||
behavior and the new. However, it is also useful to have a
|
||||
description of the new behavior that stands by itself, without
|
||||
reference to the previous model. So this next section will
|
||||
attempt to provide such a description.
|
||||
The previous section describes the difference between the old
|
||||
behavior and the new. However, it is also useful to have a
|
||||
description of the new behavior that stands by itself, without
|
||||
reference to the previous model. So this next section will
|
||||
attempt to provide such a description.
|
||||
|
||||
When a function is called, the input arguments are assigned to
|
||||
formal parameters as follows:
|
||||
When a function is called, the input arguments are assigned to
|
||||
formal parameters as follows:
|
||||
|
||||
- For each formal parameter, there is a slot which will be used
|
||||
to contain the value of the argument assigned to that
|
||||
parameter.
|
||||
- For each formal parameter, there is a slot which will be used
|
||||
to contain the value of the argument assigned to that
|
||||
parameter.
|
||||
|
||||
- Slots which have had values assigned to them are marked as
|
||||
'filled'. Slots which have no value assigned to them yet are
|
||||
considered 'empty'.
|
||||
- Slots which have had values assigned to them are marked as
|
||||
'filled'. Slots which have no value assigned to them yet are
|
||||
considered 'empty'.
|
||||
|
||||
- Initially, all slots are marked as empty.
|
||||
- Initially, all slots are marked as empty.
|
||||
|
||||
- Positional arguments are assigned first, followed by keyword
|
||||
arguments.
|
||||
- Positional arguments are assigned first, followed by keyword
|
||||
arguments.
|
||||
|
||||
- For each positional argument:
|
||||
- For each positional argument:
|
||||
|
||||
o Attempt to bind the argument to the first unfilled
|
||||
parameter slot. If the slot is not a vararg slot, then
|
||||
mark the slot as 'filled'.
|
||||
* Attempt to bind the argument to the first unfilled
|
||||
parameter slot. If the slot is not a vararg slot, then
|
||||
mark the slot as 'filled'.
|
||||
|
||||
o If the next unfilled slot is a vararg slot, and it does
|
||||
not have a name, then it is an error.
|
||||
* If the next unfilled slot is a vararg slot, and it does
|
||||
not have a name, then it is an error.
|
||||
|
||||
o Otherwise, if the next unfilled slot is a vararg slot then
|
||||
all remaining non-keyword arguments are placed into the
|
||||
vararg slot.
|
||||
* Otherwise, if the next unfilled slot is a vararg slot then
|
||||
all remaining non-keyword arguments are placed into the
|
||||
vararg slot.
|
||||
|
||||
- For each keyword argument:
|
||||
- For each keyword argument:
|
||||
|
||||
o If there is a parameter with the same name as the keyword,
|
||||
then the argument value is assigned to that parameter slot.
|
||||
However, if the parameter slot is already filled, then that
|
||||
is an error.
|
||||
* If there is a parameter with the same name as the keyword,
|
||||
then the argument value is assigned to that parameter slot.
|
||||
However, if the parameter slot is already filled, then that
|
||||
is an error.
|
||||
|
||||
o Otherwise, if there is a 'keyword dictionary' argument,
|
||||
the argument is added to the dictionary using the keyword
|
||||
name as the dictionary key, unless there is already an
|
||||
entry with that key, in which case it is an error.
|
||||
* Otherwise, if there is a 'keyword dictionary' argument,
|
||||
the argument is added to the dictionary using the keyword
|
||||
name as the dictionary key, unless there is already an
|
||||
entry with that key, in which case it is an error.
|
||||
|
||||
o Otherwise, if there is no keyword dictionary, and no
|
||||
matching named parameter, then it is an error.
|
||||
* Otherwise, if there is no keyword dictionary, and no
|
||||
matching named parameter, then it is an error.
|
||||
|
||||
- Finally:
|
||||
- Finally:
|
||||
|
||||
o If the vararg slot is not yet filled, assign an empty tuple
|
||||
as its value.
|
||||
* If the vararg slot is not yet filled, assign an empty tuple
|
||||
as its value.
|
||||
|
||||
o For each remaining empty slot: if there is a default value
|
||||
for that slot, then fill the slot with the default value.
|
||||
If there is no default value, then it is an error.
|
||||
* For each remaining empty slot: if there is a default value
|
||||
for that slot, then fill the slot with the default value.
|
||||
If there is no default value, then it is an error.
|
||||
|
||||
In accordance with the current Python implementation, any errors
|
||||
encountered will be signaled by raising TypeError. (If you want
|
||||
something different, that's a subject for a different PEP.)
|
||||
In accordance with the current Python implementation, any errors
|
||||
encountered will be signaled by raising ``TypeError``. (If you want
|
||||
something different, that's a subject for a different PEP.)
|
||||
|
||||
|
||||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
The function calling behavior specified in this PEP is a superset
|
||||
of the existing behavior - that is, it is expected that any
|
||||
existing programs will continue to work.
|
||||
The function calling behavior specified in this PEP is a superset
|
||||
of the existing behavior - that is, it is expected that any
|
||||
existing programs will continue to work.
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
coding: utf-8
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
coding: utf-8
|
||||
End:
|
||||
|
|
Loading…
Reference in New Issue