Comments (7)
added
list
to avoid it
Just in case you don't like using a list: dict(iter(result))
perhaps also works, and dict(r for r in result)
and dict(itertools.chain(result))
certainly do.
from cpython.
Since a mapping is iterable, dict_update_arg(arg)
, whose code you quoted above, must first check whether arg seems to be a mapping. If arg has .keys, it assumes that it is meant to be a mapping and unconditionally calls and returns the merge function. So keys
is enough for arg to be tried out as a mapping, but not enough for it to be successfully used as a mapping.
I believe you are claiming that either dict_update_arg
should also check for __getitem__
before calling the merge function or the doc should be changed somehow. I am not going to decide which.
Class MyListWithKeys
, which you edited out from the post above while I was writing this, is both a list and a mapping, and would pass a __getitem__
check. I presume you deleted after realizing this. However, it is a buggy mapping in that its keys
and __getitem__
are dis-coordinated. Since it also raises TypeError, it illustrates why merely checking for a TypeError is insufficient for deciding to fallback to a sequence merge.
from cpython.
Yes, I realized that class MyListWithKeys
defines both keys
and __getitem__
and in such case dict
works as intended.
It can be fixed replacing list
with set
.
class MySetWithKeys(set):
__slots__ = ()
def keys(self, /):
return list(map(repr, self))
my_set = MySetWithKeys([(1, 2), (3, 4)])
Raises TypeError
upon calling dict(my_set)
: 'MySetWithKeys' object is not subscriptable
.
A real world example of an iterable with keys
and without __getitem__
is in Neo4j driver for Python. There is a class neo4j.Result
which is an iterator with method keys
. The method returns names of variables used in the result of the query.
For example, query returns an iterable of records of size 2 where the first index is occupied by ID and the second by the list of connected IDs:
query = (
'unwind $source_ids as node_id '
'match (:Node {nodeId: node_id})--(other:Node:Entity) '
'return node_id, collect(other.nodeId) as ids'
)
result: neo4j.Result = self.session.run(query, source_ids=list(source_ids))
return dict(list(result))
Initially I used dict(result)
as neo4j.Result
is not a mapping, but then I got the error and added list
to avoid it.
from cpython.
Object is not a mapping, so the exception looks correct, not a bug.
But the experiment above shows that only keys is enough.
It shows that keys is not enough. For each key in keys, mapping[key] is accessed to get the value, and that failed.
there is no link to what is considered as a mapping object
This glossary entry points one here
I guess one is expected to know to look and find if one does not know what a mapping is. The first occurrence of mapping
in the text could/should be linked to the glossary entry just as the iterable
entry is. I'll make a PR tomorrow if no one beats me or persuades me to not do so.
from cpython.
Object is not a mapping, so the exception looks correct, not a bug.
Object is not a mapping, but it is still an iterable, so I expect dict
and dict.update
to use other method of initialization/update.
It shows that keys is not enough. For each key in keys, mapping[key] is accessed to get the value, and that failed.
It shows that the presence of keys
is enough to consider an object being mapping and then use __getitem__
to get key-value pairs even if this attribute is not present. But the comment in C states that both attributes must be present. The same typeshed
does with SupportsKeysAndGetItem
protocol. The glossary states that mapping is an object which implements collections.abc.Mapping
. It is clear, that class Object
in my report is not a mapping, yet dict
and dict.update
consider otherwise.
from cpython.
I think this can be closed. The OP's description is the actual intended and tested behavior.
Here's the spec from collections.abc.MutableMapping.update()
:
def update(self, other=(), /, **kwds):
''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.
If E present and has a .keys() method, does: for k in E: D[k] = E[k]
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
In either case, this is followed by: for k, v in F.items(): D[k] = v
'''
if isinstance(other, Mapping):
for key in other:
self[key] = other[key]
elif hasattr(other, "keys"):
for key in other.keys():
self[key] = other[key]
else:
for key, value in other:
self[key] = value
for key, value in kwds.items():
self[key] = value
Note that the docstring for dict.update
has the same specification:
>>> help(dict.update)
Help on method_descriptor:
update(...)
D.update([E, ]**F) -> None. Update D from dict/iterable E and F.
If E is present and has a .keys() method, then does: for k in E: D[k] = E[k]
If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v
In either case, this is followed by: for k in F: D[k] = F[k]
The produces exactly the behavior the OP observed. If non-mapping has keys()
, the update method with loop over those and attempt to use __getitem__
to fetch a value. If __getitem__
is not found, a TypeError is raised for a non-subscriptable argument.
So, it looks to me like the behavior the OP observed is exactly what was promised. This spec have been around a very long time and almost certainly there is code that relies on the behavior.
Since neo4j.Result
isn't working with the protocol as designed, it should probably just document the iter(result)
or list(result)
workaround.
from cpython.
Alright, that evidence is persuasive. At this point I think the docs should be synchronized and fixed.
If E present and has a .keys() method, does: for k in E: D[k] = E[k]
There, for k in E: D[k] = E[k]
should be replaced with for k in E.keys(): D[k] = E[k]
as this is what actually happens. Phrase has a .keys() method
should be changed to has a .keys attribute
.
A similar note should be provided for dict
constructor too, either in web documentation or in help message. Current docs say mapping
which by glossary terms should implement collections.abc.Mapping
but actually having keys
is enough to proceed with for k in E.keys(): D[k] = E[k]
.
Nethertheless, I find a bit awkward the fact that the behavior depends on a single non-dunder method. keys
may not be always a method after all. From now on I will be careful with any custom iterable/iterator to avoid usage of any member named keys
unless the object is indeed a mapping.
For the code below mypy does not provide any warnings to dict(Object())
. The revealed type is dict[str, int]
- the same as I expected before opening this issue.
from collections.abc import Iterator, Iterable
from typing import reveal_type
class Object:
def __iter__(self, /) -> Iterator[tuple[str, int]]: ...
def keys(self, /) -> Iterable[str]: ...
d = dict(Object())
reveal_type(d)
Maybe adding next overload to dict
and its update method would emit a warning. But I am afraid that this would cause any valid input to be also marked.
@overload
def __init__(self, iterable: SupportKeys, /) -> Never: ...
from cpython.
Related Issues (20)
- object.__sizeof__(1) have different output between 3.9.10 and 3.12.2 HOT 8
- max(a,b) sometimes depends on order of args HOT 6
- Handle leading `//` for `posixpath.commonpath` HOT 17
- compileall doesn't recompile when invalidation mode changes even if timestamps match HOT 1
- compileall with workers would is faster with larger chunksize HOT 1
- Breakpoint disables warning: sys:1: RuntimeWarning: coroutine 'status_change_listener' was never awaited HOT 7
- Connecting a socket in timeout mode to a server with full listen backlog raises BlockingIOError
- unittest.TestResult fails with exception deriving from frozen dataclass HOT 9
- Potential string parsing regression in 3.12 HOT 5
- collection.ChainMap.fromkeys argument 'value' has incorrect argument kind HOT 2
- Might mock.MagicMock be given a type parameter? HOT 2
- Move large uop bodies into functions. HOT 5
- Refresh doctest summary HOT 5
- Allow CPython to build against cryptography libraries lacking BLAKE2 support
- multiprocessing test_processes: test_thread_safety() fails with: Assertion failed: gc_list_is_empty(to) || gc_old_space(to_tail) == gc_old_space(from_tail) HOT 1
- ABCs with bogus __subclasses__ break instance/subclass checks for other ABCs HOT 1
- Fails to read shared memory of 'objects' across multiple processes on macOS HOT 4
- SystemError: <method-wrapper '__init__' of ast.AST-child object at ...> returned NULL without setting an exception HOT 7
- scandir direntry.stat() always returns st_ctime of 0 on Windows
- dis.cmp_op doc is inaccurate in Python 3.12.2 and 3.13.0a5 HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cpython.