<div dir="ltr">Couldn't you add __hash__ to collections.abc.Iterable ? Essentially, expose __hash__ there; then all iterables automatically have a default hash that hashes their ordered contents.<br><br>On Wednesday, January 4, 2017 at 7:37:26 PM UTC-5, Steven D'Aprano wrote:<blockquote class="gmail_quote" style="margin: 0;margin-left: 0.8ex;border-left: 1px #ccc solid;padding-left: 1ex;">On Wed, Jan 04, 2017 at 04:38:05PM -0500, <a href="javascript:" target="_blank" gdf-obfuscated-mailto="MfDAaNhHEgAJ" rel="nofollow" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">j...@math.brown.edu</a> wrote:
<br>> Instead of the proposals like "hash.from_iterable()", would it make sense
<br>> to allow tuple.__hash__() to accept any iterable, when called as a
<br>> classmethod?
<br>
<br>The public API for calculating the hash of something is to call the
<br>hash() builtin function on some object, e.g. to call tuple.__hash__ you
<br>write hash((a, b, c)). The __hash__ dunder method is implementation, not
<br>interface, and normally shouldn't be called directly.
<br>
<br>Unless I'm missing something obvious, your proposal would require the
<br>caller to call the dunder methods directly:
<br>
<br>class X:
<br> def __hash__(self):
<br> return tuple.__hash__(iter(self))
<br>
<br>I consider that a poor interface design.
<br>
<br>But even if we decide to make an exception in this case, tuple.__hash__
<br>is currently an ordinary instance method right now. There's probably
<br>code that relies on that fact and expects that:
<br>
<br> tuple.__hash__((a, b, c))
<br>
<br>is currently the same as
<br>
<br> (a, b, c).__hash__()
<br>
<br>
<br>(Starting with the hash() builtin itself, I expect, although that is
<br>easy enough to fix if needed.) Your proposal will break backwards
<br>compatibility, as it requires a change in semantics:
<br>
<br>(1) (a, b, c).__hash__() must keep the current behaviour, which
<br>means behaving like a bound instance method;
<br>
<br>(2) But tuple.__hash__ will no longer return an unbound method (actually
<br>a function object, but the difference is unimportant) and instead will
<br>return something that behaves like a bound class method.
<br>
<br>Here's an implementation which does this:
<br>
<br><a href="http://code.activestate.com/recipes/577030-dualmethod-descriptor/" target="_blank" rel="nofollow" onmousedown="this.href='http://www.google.com/url?q\x3dhttp%3A%2F%2Fcode.activestate.com%2Frecipes%2F577030-dualmethod-descriptor%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNEWP1g_F5hgk0knGWtMVmgH98QGFQ';return true;" onclick="this.href='http://www.google.com/url?q\x3dhttp%3A%2F%2Fcode.activestate.com%2Frecipes%2F577030-dualmethod-descriptor%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNEWP1g_F5hgk0knGWtMVmgH98QGFQ';return true;">http://code.activestate.com/<wbr>recipes/577030-dualmethod-<wbr>descriptor/</a>
<br>
<br>so such a thing is possible. But it breaks backwards-compatability and
<br>introduces something which I consider to be an unclean API (calling a
<br>dunder method directly). Unless there's a *really* strong advantage to
<br>
<br> tuple.__hash__(...)
<br>
<br>over
<br>
<br> hash.from_iterable(...)
<br>
<br>(or equivalent), I would be against this change.
<br>
<br>
<br>
<br>> (And similarly with frozenset.__hash__(), so that the fast C
<br>> implementation of that algorithm could be used, rather than the slow
<br>> collections.Set._hash() implementation. Then the duplicated implementation
<br>> in _collections_abc.py's Set._hash() could be removed completely,
<br>> delegating to frozenset.__hash__() instead.)
<br>
<br>This is a good point. Until now, I've been assuming that
<br>hash.from_iterable should consider order. But frozenset shows us that
<br>sometimes the hash should *not* consider order.
<br>
<br>This hints that perhaps the hash.from_iterable() should have its own
<br>optional dunder method. Or maybe we need two functions: an ordered
<br>version and an unordered version.
<br>
<br>Hmmm... just tossing out a wild idea here... let's get rid of the dunder
<br>method part of your suggestion, and add new public class methods to
<br>tuple and frozenset:
<br>
<br> tuple.hash_from_iter(iterable)
<br> frozenset.hash_from_iter(<wbr>iterable)
<br>
<br>
<br>That gets rid of all the objections about backwards compatibility, since
<br>these are new methods. They're not dunder names, so there are no
<br>objections to being used as part of the public API.
<br>
<br>A possible objection is the question, is this functionality *actually*
<br>important enough to bother?
<br>
<br>Another possible objection: are these methods part of the sequence/set
<br>API? If not, do they really belong on the tuple/frozenset? Maybe they
<br>belong elsewhere?
<br>
<br>
<br>
<br>> Would this API more cleanly communicate the algorithm being used and the
<br>> implementation,
<br>
<br>No. If you want to communicate the algorithm being used, write some
<br>documentation.
<br>
<br>Seriously, the public API doesn't communicate the algorithm used for the
<br>implementation. How can it? We can keep the same interface and change
<br>the implementation, or change the interface and keep the implementation.
<br>The two are (mostly) independent.
<br>
<br>
<br>
<br>> while making a smaller increase in API surface area
<br>> compared to introducing a new function?
<br>
<br>It's difficult to quantify "API surface area". On the one hand, we have
<br>the addition of one or two new functions or methods. Contrast with:
<br>
<br> * introducing a new kind of method into the built-ins (one which
<br> behaves like a classmethod when called from the class, and like
<br> an instance method when called from an instance);
<br>
<br> * changing tuple.__hash__ from an ordinary method to one of the
<br> above special methods;
<br>
<br> * and likewise for frozenset.__hash__;
<br>
<br> * change __hash__ from "only used as implementation, not as
<br> interface" to "sometimes used as interface".
<br>
<br>
<br>To me, adding one or two new methods/functions is the smaller, or at
<br>least less disruptive, change.
<br>
<br>
<br>
<br>--
<br>Steve
<br>______________________________<wbr>_________________
<br>Python-ideas mailing list
<br><a href="javascript:" target="_blank" gdf-obfuscated-mailto="MfDAaNhHEgAJ" rel="nofollow" onmousedown="this.href='javascript:';return true;" onclick="this.href='javascript:';return true;">Python...@python.org</a>
<br><a href="https://mail.python.org/mailman/listinfo/python-ideas" target="_blank" rel="nofollow" onmousedown="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fmailman%2Flistinfo%2Fpython-ideas\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFj1EaNHnVmh20FnFPoUi4J-MpfQw';return true;" onclick="this.href='https://www.google.com/url?q\x3dhttps%3A%2F%2Fmail.python.org%2Fmailman%2Flistinfo%2Fpython-ideas\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFj1EaNHnVmh20FnFPoUi4J-MpfQw';return true;">https://mail.python.org/<wbr>mailman/listinfo/python-ideas</a>
<br>Code of Conduct: <a href="http://python.org/psf/codeofconduct/" target="_blank" rel="nofollow" onmousedown="this.href='http://www.google.com/url?q\x3dhttp%3A%2F%2Fpython.org%2Fpsf%2Fcodeofconduct%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNHJOrArSUDKkjrnthO6_CznMzkPsA';return true;" onclick="this.href='http://www.google.com/url?q\x3dhttp%3A%2F%2Fpython.org%2Fpsf%2Fcodeofconduct%2F\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNHJOrArSUDKkjrnthO6_CznMzkPsA';return true;">http://python.org/psf/<wbr>codeofconduct/</a>
<br></blockquote></div>