My point is: we dont need to restrict annotations in any way. If some
libraries want to share annotations that means they are tightly enough
coupled and can make rules for itself. All other code can go in the
wild.
> _______________________________________________
> Python-ideas mailing list
> Python...@python.org
> http://mail.python.org/mailman/listinfo/python-ideas
>
--
Thanks,
Andrew Svetlov
_______________________________________________
Python-ideas mailing list
Python...@python.org
http://mail.python.org/mailman/listinfo/python-ideas
By being tolerant and well behaved when confronted with annotations that our library doesn't understand I, Ithink we can use function annotations without a short-range decorator that translates their information in some otherstructure. If other annotation-using libraries are also willing to ignore our tabbing annotations if/when they encounter them,then can't we all get along smoothly?(For reference, the feature will look/work something like this)In[1]: def foo(filename : tab_glob('*.txt')): # tab completion that recommends files/directories that match a glob pattern... pass...In[2]: foo(<TAB>'a.txt' 'b.txt''c.txt' 'dir/'
I think annotations are potentially very useful for things like introspection and static analysis. For instance, your IDE could warn you if you pass a parameter that doesn't match the type specified in an annotation. In these cases, the code reading the annotations isn't coupled with the function definitions.
I'm not aiming to restrict annotations, just to establish some conventions to make them useful. We have a convention, for instance, that attributes with a leading underscore are private. That's a useful basis that everyone understands, so when you do obj.<tab> in IPython, it doesn't
show those attributes by default. I'd like to have some conventions of that nature around annotations.
Nick,Thanks! You make a very convincing argument.Especially if this represents the collective recommendation of the python core development team on the proper conventions surrounding the use of function annotations, I would encourage you guys to perhaps make it more widely known (blogs, etc). As python 3.x adoption continues to move forward, this type of thing could become an issue if shmucks like me start using the annotation feature more widely.
However, the flip-side of the argument is that if we assume my opinion is correct and document it as an official recommendation in PEP 8, then many people won't even *try* to come up with good approaches to composition for function annotations. Maybe there *is* an elegant, natural solution out there that's superior to using explicit calls to decorator factories for the cases that involve composition. If PEP 8 declares "just use decorator factories for cases involving composition, and always design your APIs with a non-annotation based fallback for such cases", would we be inadvertently shutting down at least some of the very experimentation we intended to allow?
> I fear that this was a strategic mistake. The result, it seems to me, is that
> annotations have been badly neglected.
>
> I can't speak for others, but I heavily use the standard library as a guide
> to what counts as good practice in Python. I'm not a big user of third party
> libraries, and most of those are for 2.x, so with the lack of annotations in
> the std lib I've had no guidance as to what sort of things annotations could
> be used for apart from "type checking".
>
> I'm sure that I'm not the only one.
>
>
>
> --
> Steven
>
+1
Admittedly this was long enough ago that I don't remember the details, just the obvious consequence that PEP 8 remains largely silent on the matter, aside from declaring that function annotations are off-limits for standard library modules:
The Python standard library will not use function annotations as that would result in a premature commitment to a particular annotation style. Instead, the annotations are left for users to discover and experiment with useful annotation styles.
Early core developer attempts to use function annotations revealed inconsistent, ad-hoc annotation styles. For example:
In short, we have discovered that declarative typing isn't very
useful :-)
Regards
Antoine.
> .. but haven't thought of any other useful applications of
> annotations, and nor has the collective community on PyPI.
On Dec 2, 2012, at 3:43 AM, Nick Coghlan <ncog...@gmail.com> wrote:Admittedly this was long enough ago that I don't remember the details, just the obvious consequence that PEP 8 remains largely silent on the matter, aside from declaring that function annotations are off-limits for standard library modules:PEP 8 is not "largely silent" on the subject:
On Mon, Dec 3, 2012 at 1:08 PM, Nick Coghlan <ncog...@gmail.com> wrote:My 5 cents: perhaps you don't need to use __annotations__ at all,
> * the advisability of using the __annotations__ dictionary for long-term
> introspection, rather than using the decorator to move the information to a
> purpose-specific location in a separate function attribute
Signature object (PEP 362) gives more convenient way for gathering
information about function spec.
So long as any type hinting semantics are associated with a "@type_hints" decorator, none of those ideas conflict with my suggestions for good annotation usage practices.
The explicit decorators effectively end up serving as dialect specifiers for the annotations, for the benefit of other software (by moving the metadata out to purpose specific attributes) and for readers (simply by being present).
Anyway, the reactions here confirmed my recollection of a lack of consensus amongst the core team. I'll just put something up on my own site, instead.
Cheers,
Nick.
--
Sent from my phone, thus the relative brevity :)
The local decorator influence might work, but that has the problem of only being able to be used once before we fall back to the old method. Would you rather:
@tab_expand(filename=glob('*.txt'))
@types
def read_from_filename(filename:str, num_bytes:int) -> bytes:
pass
or
@tab_expand(filename=glob('*.txt'))
@types(filename=str, num_bytes=int, return_=bytes)
def read_from_filename(filename, num_bytes):
pass
For consistency's sake, I'd prefer the latter.
- Unions. We need a way to say "either X or Y". Given that we're
defining our own objects we may actually be able to get away with
writing e.g. "Int | Str" or "Str | List[Str]", and isinstance() would
still work. It would also be useful to have a shorthand for "either T
or None", written as Optional[T] or Optional(T).
Dict(a=Int, b=Int | None, c=Optional(Int))
where (3, None) matches as does (3, 'a', 4) but not (3, None, None).Tuple[Int, Str | None, Optional(Int)]
Tuple[Int, Optional(Int | None), Int | Str, Int | None]
Those are not the semantics I had in mind for Optional.
- Composability (Nick's pet peeve, in that he is against it). I
propose that we reserve plain tuples for this. If an annotation has
the form "x: (P, Q)" then that ought to mean that x must conform to
both P and Q. Even though Nick doesn't like this, I don't think we
should do everything with decorators. Surly, the decorators approach
is good for certain use cases, and should take precedence if it is
used. But e.g. IDEs that use annotations for suggestions and
refactoring should not require everything to be decorated -- that
would just make the code too busy.
On Tue, Dec 4, 2012 at 1:37 AM, David Townshend <aquav...@gmail.com> wrote:You've got to be kidding...
> Just thought of a couple of usages which don't fit into the decorator model.
> The first is using the return annotation for early binding:
>
> def func(seq) -> dict(sorted=sorted):
> return func.__annotations__['return']['sorted'](seq)
Surely that's some kind of random variation. It's only a 2% difference.
> Stangely enough, this seems to run slightly faster than
>
> def func(seq, sorted=sorted):
> return sorted(seq)
>
> My test shows the first running in about 0.376s and the second in about
> 0.382s (python 3.3, 64bit).
IOW, this is not a line of thought to pursue.
def defaults_as_parameter_metadata(f):names, args_name, kwargs_name, defaults = inspect.getargspec(f)assert len(names) == len(defaults) # To keep this example simple...
f.parameter_metadata = {}
for name, meta in zip(names, defaults):f.parameter_metadata[name] = metaf.__defaults__ = () # Again, for simplicity.return f
@defaults_as_parameter_metadata
def make_ice_cream(flavor=(options('vanilla', 'chocolate', ...),
str, "What kind of delicious do you want?"),quantity=(positive, double,"How much (in pounds) do you want?")):...
Function annotations (PEP 3107) are a very interesting new feature, but so far have gone largely unused. The only project I've seen using them is plac, a command-line option parser. One reason for this is that because function annotations can be used to mean anything, we're wary of doing anything in case we interfere with some other use case. A recent thread on ipython-dev touched on this [1], and we'd like to suggest some conventions to make annotations useful for everyone.
1. Code inspecting annotations should be prepared to ignore annotations it can't understand.
2. Code creating annotations should use wrapper classes to indicate what the annotation means. For instance, we are contemplating a way to specify options for a parameter, to be used in tab completion, so we would do something like this:
from IPython.core.completer import options
def my_io(filename, mode: options('read','write') ='read'):
...
3. There are a couple of important exceptions to 2:
- Annotations that are simply a string can be used like a docstring, to be displayed to the user. Inspecting code should not expect to be able to parse any machine-readable information out of these strings.
- Annotations that are a built-in type (int, str, etc.) indicate that the value should always be an instance of that type. Inspecting code may use these for type checking, introspection, optimisation, or other such purposes. Note that for now, I have limited this to built-in types, so other types can be used for other purposes, but this could be extended. For instance, the ABCs from collections (collections.Mapping et al.) could well be added to this category.
4. There should be a convention for attaching multiple annotations to one value. I propose that all code using annotations expects to handle tuples/lists of annotations. (We also considered dictionaries, but the result is long and ugly). So in this definition:
def my_io(filename, mode: (options('read','write'), str, 'The mode in which to open the file') ='read'):
...
the mode parameter has a set of options (ignored by frameworks that don't recognise it), should always be a string, and has a description.
Any thoughts and suggestions are welcome.
As an aside, we may also create a couple of decorators to fill in __annotations__ on Python 2, something like:
@return_annotation('A file obect')
@annotations(mode=(options('read','write'), str, 'The mode in which to open the file'))
def my_io(filename, mode='read'):
...
[1] http://mail.scipy.org/pipermail/ipython-dev/2012-November/010697.html
Thanks,
Thomas