This issue tracker has been migrated to GitHub ,
and is currently read-only.
For more information,
see the GitHub FAQs in the Python's Developer Guide.
Created on 2009年02月02日 19:57 by paul.moore, last changed 2022年04月11日 14:56 by admin. This issue is now closed.
| Files | ||||
|---|---|---|---|---|
| File name | Uploaded | Description | Edit | |
| generic.patch | paul.moore, 2009年02月02日 19:57 | Patch against current Python trunk | ||
| generic.patch | paul.moore, 2009年02月04日 08:27 | Updated patch incorporating Ryan's feedback | ||
| generic.patch | paul.moore, 2009年02月06日 19:03 | Version 3 - incorporated further feedback | ||
| Messages (30) | |||
|---|---|---|---|
| msg80986 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月02日 19:57 | |
This patch takes the existing "simplegeneric" decorator, currently an internal implementation detail of the pkgutil module, and exposes it as a feature of the functools module. Documentation and tests have been added, and the pkgutil code has been updated to use the functools implementation. Open issue: The syntax for registering an overload is rather manual: def xxx_impl(xxx): pass generic_fn.register(XXX, xxx_impl) It might be better to make the registration function a decorator: @generic_fn.register(XXX) def xxx_impl(xxx): pass However, this would involve changing the existing (working) code, and I didn't want to do that before there was agreement that the general idea (of exposing the functionality) was sound. |
|||
| msg81125 - (view) | Author: Ryan Freckleton (ryan.freckleton) | Date: 2009年02月04日 02:43 | |
PJE seems to have borrowed the time machine :-). Based on the code the register function is already a decorator: def register(typ, func=None): if func is None: return lambda f: register(typ, f) registry[typ] = func return func The returned lambda is a one argument decorator. so your syntax: @generic_fn.register(XXX) def xxx_impl(xxx): pass Already works. A test to validate this behavior should probably be added. I don't mean to bikeshed, but could we call this function functools.generic instead of functools.simplegeneric? The only reason I can think of for keeping it simplegeneric would be to avoid a future name clash with the Generic Function PEP and if/when that PEP get's implemented, I would think that the functionality would live in builtins, not functools. |
|||
| msg81129 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月04日 08:10 | |
Well spotted! I missed that when I checked. I will add tests and documentation. I agree that generic is better. I only left it as it was because the original intent was simply to move the existing code - but that's not a particularly good reason for keeping a clumsy name. There shouldn't be a clash, as any more general mechanism can either be in its own module or the existing function can be extended in a compatible manner. I'll make this change too. Thanks for the feedback! |
|||
| msg81130 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月04日 08:27 | |
Here's an updated patch. |
|||
| msg81137 - (view) | Author: Alyssa Coghlan (ncoghlan) * (Python committer) | Date: 2009年02月04日 11:48 | |
The reason I like the simplegeneric name is that that is exactly what this feature is: a *simple* generic implementation that is deliberately limited to dispatching on the first argument (because that is easily explained to users that are already familiar with OOP and especially the existing Python magic method dispatch mechanism. So the name isn't just about avoiding name clashes, it's also about setting appropriate expectations as to what is supported. Yes, the name is a little clumsy but one thing I do *not* want to see happen is a swathe of feature requests asking that this become an all-singing all-dancing generic function mechanism like RuleDispatch. Don't forget that actually *writing* generic functions (i.e. using the @functools.simplegeneric decorator itself) should be far less common than using the .register() method of existing generic functions. |
|||
| msg81138 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月04日 12:03 | |
Fair comment. As Ryan said, it's a bit of a bikeshed issue. I prefer "generic", on the basis that I'd prefer to keep the simple name for the simple use - something as complex as the RuleDispatch version could use the name "dispatch" (if they want to keep it the name simple) or "multimethod" (to emphasize that it dispatches on more than just one argument). If the consensus is for keeping it as "simplegeneric", I'll go with that, but I'll wait for other views first. BTW, another option is simply to clarify the limitations in the documentation. I can add something there if that would be enough for you. |
|||
| msg81156 - (view) | Author: Walter Dörwald (doerwalter) * (Python committer) | Date: 2009年02月04日 20:19 | |
The patch looks fine to me. Tests pass. I have no opinion about the name. Both "simplegeneric" and "generic" are OK to me. I wonder if being able to use register() directly instead of as a decorator should be dropped. Also IMHO the Python 2.3 backwards compatibility (__name__ isn't setable) can be dropped. |
|||
| msg81177 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月04日 22:52 | |
Agreed about the compatibility. It's there from pkgutil, where to be honest, it's even less necessary, as simplegeneric was for internal use only, there. I'm certainly not aware of any backward compatibility requirements for functools. Assuming nobody speaks up to the contrary, I'll rip out the compatibility bits in the next version of the patch. I'm unsure about the non-decorator version of register. I can imagine use cases for it - consider pprint, for example, where you might want to register str as the overload for your particular type. But it's not a big deal either way. |
|||
| msg81186 - (view) | Author: Ryan Freckleton (ryan.freckleton) | Date: 2009年02月05日 03:30 | |
I think that registering existing functions is an important use case, so I vote for keeping the non-decorator version of register. Another thing that we may want to document is that [simple]generic doesn't dispatch based on registered abstract base classes. >>> class A: ... pass ... >>> class C: ... __metaclass__ = abc.ABCMeta ... >>> C.register(A) >>> @generic ... def pprint(obj): ... print str(obj) ... >>> @pprint.register(C) ... def pprint_C(obj): ... print "Charlie", obj ... >>> pprint(C()) Charlie <__main__.C object at 0xb7c5336c> >>> pprint(A()) <__main__.A instance at 0xb7c5336c> |
|||
| msg81201 - (view) | Author: Alyssa Coghlan (ncoghlan) * (Python committer) | Date: 2009年02月05日 10:58 | |
Failure to respect isinstance() should be fixed, not documented :) As far as registering existing functions goes, I also expect registering lambdas and functools.partial will be popular approaches, so keeping direct registration is a good idea. There isn't any ambiguity between the one-argument and two-argument forms. |
|||
| msg81210 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月05日 11:59 | |
Agreed (in principle). However, in practice the subtleties of override order must be documented (and a method of implementation must be established!!!) Consider: >>> class A: ... pass ... >>> class C: ... __metaclass__ = abc.ABCMeta ... >>> class D: ... __metaclass__ = abc.ABCMeta ... >>> C.register(A) >>> D.register(A) >>> @generic ... def pprint(obj): ... print "Base", str(obj) ... >>> @pprint.register(C) ... def pprint_C(obj): ... print "Charlie", obj ... >>> @pprint.register(D) ... def pprint_D(obj): ... print "Delta", obj ... >>> pprint(A()) What should be printed? A() is a C and a D, but which takes precedence? There is no concept of a MRO for ABCs, so how would the "correct" answer be defined? "Neither" may not be perfect, but at least it's clearly defined. Relying on order of registration for overloads of the generic function seems to me to be unacceptable, before anyone suggests it, as it introduces a dependency on what order code is imported. So while the theory makes sense, the practice is not so clear. Respecting ABCs seems to me to contradict the "simple" aspect of simplegeneric, so a documented limitation is appropriate. (But given the above, I'm more inclined now to leave the name as "simplegeneric", precisely to make this point :-)) |
|||
| msg81211 - (view) | Author: Alyssa Coghlan (ncoghlan) * (Python committer) | Date: 2009年02月05日 12:16 | |
Hmm, there is such a thing as being *too* simple... a generic function implementation that doesn't even respect ABCs seems pretty pointless to me (e.g. I'd like to be able to register a default Sequence implementation for pprint and have all declared Sequences use it automatically if there isn't a more specific override). I'll wait until I have a chance to actually play with the code a bit before I comment further though. |
|||
| msg81212 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月05日 13:20 | |
Very good point. Registering for the standard ABCs seems like an
important use case. Unfortunately, it seems to me that ABCs simply don't
provide that capability - is there a way, for a given class, of listing
all the ABCs it's registered under? Even if the order is arbitrary,
that's OK.
Without that, I fail to see how *any* generic function implementation
("simple" or not) could support ABCs. (Excluding obviously broken
approaches such as registration-order dependent overload resolution).
The problem is that ABCs are all about isinstance testing, where generic
functions are all about *avoiding* isinstance testing. (As a compromise,
you could have a base generic function that did isinstance testing for
the sequence ABC).
|
|||
| msg81222 - (view) | Author: Alyssa Coghlan (ncoghlan) * (Python committer) | Date: 2009年02月05日 20:03 | |
Even more inconveniently, the existence of unregister() on ABCs makes it difficult for the generic to cache the results of the isinstance() checks (you don't want to be going through the chain of registered ABCs every time calling isinstance(), since that would be painfully slow). That said, it is already the case that if you only *register* with an ABC, you don't get any of the methods - you have to implement them yourself. It's only when you actually *inherit* from the ABC that the methods are provided "for free". I guess the case isn't really any different here - if you changed your example so that A inherited from C and D rather than merely registering with them, then C & D would appear in the MRO and the generic would recognise them. So perhaps just documenting the limitation is the right answer after all. |
|||
| msg81291 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年02月06日 19:03 | |
Here's an updated patch. I've reverted to the name "simplegeneric" and documented the limitation around ABCs (I've tried to give an explanation why it's there, as well as a hint on now to work around the limitation - let me know if I'm overdoing it, or the text needs rewording). I've also fixed the wrapper to use update_wrapper to copy the attributes. That way, there's no duplication. |
|||
| msg82990 - (view) | Author: Alyssa Coghlan (ncoghlan) * (Python committer) | Date: 2009年03月01日 21:32 | |
Unassigning - the lack of support for ABC registration still bothers me, but a) I don't have a good answer for it, and b) I'm going to be busy for a while working on some proposed changes to the with statement. |
|||
| msg83009 - (view) | Author: Kevin Teague (kteague) | Date: 2009年03月02日 09:39 | |
The problem with generic functions supporting ABCs is it's a bug with the way ABCs work and not a problem with the generic function implementation. The register() method of an ABC only fakes out isinstance checks, it doesn't actually make the abstract base class a base class of the class. It doesn't make any sense for a class to say it is an instance of an ABC, but not have that ABC in it's MRO. It's not a base class if it's not in the MRO! The documentation for lack of ABC support should read something like: + Note that generic functions do not work with classes which have + been declared as an abstract base class using the + abc.ABCMeta.register() method because this method doesn't make + that abstract base class a base class of the class - it only fakes + out instance checks. Perhaps a bug should be opened for the abc.ABCMeta.register() method. However, I'd say that just because virtual abstract base classes are wonky doesn't mean that a solid generic function implementation shouldn't be added to standard library. |
|||
| msg83017 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2009年03月02日 13:21 | |
I raised issue 5405. Armin Ronacher commented over there that it's not even possible in principle to enumerate the ABCs a class implements because ABCs can do semantic checks (e.g., checking for the existence of a special method). So documenting the limitation is all we can manage, I guess. |
|||
| msg83036 - (view) | Author: Alyssa Coghlan (ncoghlan) * (Python committer) | Date: 2009年03月02日 20:53 | |
Given the point Armin raised, I once again agree that documenting the limitation is a reasonable approach. Longer-term, being able to subcribe to ABCs (and exposing the registration list if it isn't already visible) is likely to be the ultimate solution. |
|||
| msg92573 - (view) | Author: Guido van Rossum (gvanrossum) * (Python committer) | Date: 2009年09月13日 17:07 | |
Please don't introduce this without a PEP. |
|||
| msg111056 - (view) | Author: Mark Lawrence (BreamoreBoy) * | Date: 2010年07月21日 13:53 | |
Changes as Guido has stated that he wants a PEP. |
|||
| msg111167 - (view) | Author: Paul Moore (paul.moore) * (Python committer) | Date: 2010年07月22日 11:24 | |
I don't propose to raise a PEP myself. The issue with ABCs seems to me to be a fundamental design issue, and I think it's better to leave raising any PEP, and managing the subsequent discussion, to someone with a deeper understanding of, and interest in, generic functions. Not sure if the lack of a champion means that this issue should be closed. I'm happy if that's the consensus (but I'm also OK with it being left open indefinitely, until someone cares enough to pick it up). |
|||
| msg111169 - (view) | Author: Antoine Pitrou (pitrou) * (Python committer) | Date: 2010年07月22日 11:45 | |
Generic functions are a lesser-known paradigm than OO, and nowhere do common Python documents (including the official docs) try to teach them. That means the first public appearance of generic functions in the stdlib should really be well thought out if we don't want to encourage poor practices. I agree with Guido that a PEP is required to flesh out all the details. |
|||
| msg111212 - (view) | Author: Ryan Freckleton (ryan.freckleton) | Date: 2010年07月22日 19:18 | |
An elaborate PEP for generic functions already exists, PEP 3124 [ http://www.python.org/dev/peps/pep-3124/]. Also note the reasons for deferment. I'd be interested in creating a "more limited" generic function implementation based on this PEP, minus func_code rewriting and the other fancier items. Sadly I won't have any bandwidth to work on it until January of next year. I'd vote for keeping this issue open because of that. On Thu, Jul 22, 2010 at 5:45 AM, Antoine Pitrou <report@bugs.python.org>wrote: > > Antoine Pitrou <pitrou@free.fr> added the comment: > > Generic functions are a lesser-known paradigm than OO, and nowhere do > common Python documents (including the official docs) try to teach them. > That means the first public appearance of generic functions in the stdlib > should really be well thought out if we don't want to encourage poor > practices. I agree with Guido that a PEP is required to flesh out all the > details. > > ---------- > nosy: +pitrou > > _______________________________________ > Python tracker <report@bugs.python.org> > <http://bugs.python.org/issue5135> > _______________________________________ > |
|||
| msg112061 - (view) | Author: Alyssa Coghlan (ncoghlan) * (Python committer) | Date: 2010年07月30日 10:46 | |
A couple more relevant links. I brought this issue up in the context of a JSON serialisation discussion on python-ideas: http://mail.python.org/pipermail/python-ideas/2010-July/007854.html Andrey Popp mentioned his pure Python generic functions library in that thread: http://pypi.python.org/pypi/generic |
|||
| msg132176 - (view) | Author: Éric Araujo (eric.araujo) * (Python committer) | Date: 2011年03月25日 22:45 | |
> The register() method of an ABC only fakes out isinstance checks, it > doesn't actually make the abstract base class a base class of the class. > It doesn't make any sense for a class to say it is an instance of an > ABC, but not have that ABC in [its] MRO. I disagree. If someone writes a class and registers them with an ABC, it is their duty to make sure that the class actually complies. Virtual subclasses are provided for use by consenting adults IMO. |
|||
| msg132206 - (view) | Author: PJ Eby (pje) * (Python committer) | Date: 2011年03月26日 01:37 | |
Just as an FYI, it *is* possible to do generic functions that work with Python's ABCs (PEAK-Rules supports it for Python 2.6), but it requires caching, and a way of handling ambiguities. In PEAK-Rules' case, unregistering is simply ignored, and ambiguity causes an error at call time. But simplegeneric can avoid ambiguities, since it's strictly single-dispatch. Basically, you just have two dictionaries instead of one. The first dictionary is the same registry that's used now, but the second is a cache of "virtual MROs" you'll use in place of a class' real MRO. The virtual MRO is built by walking the registry for classes that the class is a subclass of, but which are *not* found in the class's MRO, e.g.: for rule_cls in registry: if issubclass(cls, rule_cls) and rule_cls not in real_mro: # insert rule_cls into virtual_mro for cls You then insert those classes (abcs) in the virtual MRO at the point just *after* the last class in the MRO that says it's a subclass of the abc in question. IOW, you implement it such that an abc declaration appears in the MRO just after the class that was registered for it. (This has to be recursive, btw, and the MRO cache has to be cleared when a new method is registered with the generic function.) This approach, while not trivial, is still "simple", in that it has a consistent, unambiguous resolution order. Its main downside is that it holds references to the types of objects it has been called with. (But that could be worked around with a weak key dictionary, I suppose.) It also doesn't reset the cache on unregistration of an abc subclass, and it will be a bit slower on the first call with a previously-unseen type. The downside of a PEP, to me, is that it will be tempting to go the full overloading route -- which isn't necessarily a bad thing, but it won't be a *simple* thing, and it'll be harder to get agreement on what it should do and how -- especially with respect to resolution order. Still, if someone wants to do a PEP on *simple* generics -- especially one that can replace pkgutil.simplegeneric, and could be used to refactor things like copy.copy, pprint.pprint, et al to use a standardized registration mechanism, I'm all in favor -- with or without abc registration support. Btw, the current patch on this issue includes code that is there to support classic classes, and metaclasses written in C. Neither should be necessary in 3.x. Also, a 3.x version could easily take advantage of type signatures, so that: @foo.register def foo_bar(baz: bar): ... could be used instead of @foo.register(bar, foo_bar). But all that would be PEP territory, I suppose. |
|||
| msg132207 - (view) | Author: Éric Araujo (eric.araujo) * (Python committer) | Date: 2011年03月26日 01:40 | |
Thanks for the detailed explanation. Note that type annotations are disallowed in the stdlib, as per PEP 8. |
|||
| msg185234 - (view) | Author: Terry J. Reedy (terry.reedy) * (Python committer) | Date: 2013年03月25日 21:04 | |
Guido said "Please don't introduce this without a PEP." and that has not happened and if it did, the result would probably look quite different from the patches. So this is 'unripe' and no current action here is possible. |
|||
| msg190773 - (view) | Author: Łukasz Langa (lukasz.langa) * (Python committer) | Date: 2013年06月07日 20:09 | |
For the record, this has been implemented as PEP 443. |
|||
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2022年04月11日 14:56:45 | admin | set | github: 49385 |
| 2013年06月07日 20:09:35 | lukasz.langa | set | nosy:
+ lukasz.langa messages: + msg190773 |
| 2013年03月26日 02:44:26 | eric.snow | set | nosy:
+ eric.snow |
| 2013年03月25日 21:04:56 | terry.reedy | set | stage: resolved |
| 2013年03月25日 21:04:43 | terry.reedy | set | status: open -> closed nosy: + terry.reedy messages: + msg185234 resolution: later |
| 2011年03月26日 08:23:12 | daniel.urban | set | nosy:
+ daniel.urban |
| 2011年03月26日 01:40:55 | eric.araujo | set | messages: + msg132207 |
| 2011年03月26日 01:37:33 | pje | set | messages: + msg132206 |
| 2011年03月25日 22:45:57 | eric.araujo | set | nosy: + pje, - BreamoreBoy |
| 2011年03月25日 22:45:18 | eric.araujo | set | nosy:
+ rhettinger messages: + msg132176 |
| 2010年10月19日 18:16:25 | eric.araujo | set | files: - unnamed |
| 2010年07月30日 12:01:06 | eric.araujo | set | nosy:
+ eric.araujo |
| 2010年07月30日 10:46:52 | ncoghlan | set | messages: + msg112061 |
| 2010年07月22日 19:18:15 | ryan.freckleton | set | files:
+ unnamed messages: + msg111212 |
| 2010年07月22日 11:45:17 | pitrou | set | nosy:
+ pitrou messages: + msg111169 |
| 2010年07月22日 11:24:03 | paul.moore | set | messages: + msg111167 |
| 2010年07月21日 13:53:16 | BreamoreBoy | set | versions:
+ Python 3.3, - Python 2.7 nosy: + BreamoreBoy messages: + msg111056 stage: patch review -> (no value) |
| 2009年09月13日 17:07:30 | gvanrossum | set | nosy:
+ gvanrossum messages: + msg92573 |
| 2009年03月02日 20:53:36 | ncoghlan | set | messages: + msg83036 |
| 2009年03月02日 13:21:27 | paul.moore | set | messages: + msg83017 |
| 2009年03月02日 13:21:10 | paul.moore | set | messages: - msg83016 |
| 2009年03月02日 13:20:43 | paul.moore | set | messages: + msg83016 |
| 2009年03月02日 09:39:10 | kteague | set | nosy:
+ kteague messages: + msg83009 |
| 2009年03月01日 21:32:10 | ncoghlan | set | assignee: ncoghlan -> messages: + msg82990 |
| 2009年02月06日 19:03:18 | paul.moore | set | files:
+ generic.patch messages: + msg81291 |
| 2009年02月05日 20:03:22 | ncoghlan | set | messages: + msg81222 |
| 2009年02月05日 13:20:02 | paul.moore | set | messages: + msg81212 |
| 2009年02月05日 12:16:24 | ncoghlan | set | messages: + msg81211 |
| 2009年02月05日 11:59:36 | paul.moore | set | messages: + msg81210 |
| 2009年02月05日 10:58:19 | ncoghlan | set | messages: + msg81201 |
| 2009年02月05日 03:30:09 | ryan.freckleton | set | messages: + msg81186 |
| 2009年02月04日 22:52:47 | paul.moore | set | messages: + msg81177 |
| 2009年02月04日 20:19:41 | doerwalter | set | nosy:
+ doerwalter messages: + msg81156 |
| 2009年02月04日 12:03:16 | paul.moore | set | messages: + msg81138 |
| 2009年02月04日 11:48:08 | ncoghlan | set | messages: + msg81137 |
| 2009年02月04日 08:27:39 | paul.moore | set | files:
+ generic.patch messages: + msg81130 |
| 2009年02月04日 08:10:11 | paul.moore | set | messages: + msg81129 |
| 2009年02月04日 02:43:20 | ryan.freckleton | set | nosy:
+ ryan.freckleton messages: + msg81125 |
| 2009年02月02日 19:57:38 | paul.moore | create | |