1

Consider the following toy class:

class Something(object): def __init__(self, a, b): self.a = a self.b = b def __iter__(self): yield ('a', self.a) yield ('b', self.b) x = Something(1, 2) print(tuple(x)) # (('a', 1), ('b', 2)) print(dict(x)) # {'a': 1, 'b': 2} 

However, I would like it to behave like the following:

print(tuple(x)) # (1, 2) print(dict(x)) # {'a': 1, 'b': 2} 

How could I do that?


EDIT

I am well aware that this could be achieved explicitly, e.g. (following dict() naming conventions):

class Something(object): def __init__(self, a, b): self.a = a self.b = b def items(self): yield ('a', self.a) yield ('b', self.b) def values(self): yield self.a yield self.b 

BUT clearly some objects do behave differently when casted to dict() or tuple() respectively. For example, the behavior of dict() itself (but also, for example, collections.OrderedDict and other mappings in the collections module) do something similar (uses keys while I would like to get values) just fine:

import collections dd = collections.OrderedDict((('a', 1), ('b', 2))) print(dict(dd)) {'a': 1, 'b': 2} print(tuple(dd)) ('a', 'b') print([x for x in dd]) ['a', 'b'] 

EDIT 2:

Another way of seeing this is that when something goes through dict() EITHER it behaves differently depending on the type of __iter__ OR looks like sometimes it relies on __iter__ and sometimes it relies on something else. The question is what is that something else (or what kind of type checks happen at this level), how to get access to this alternate behavior and eventually discussing potential limitations.

I could well be that at the end of the day a custom class Something behaving as I described cannot be crafted in Python, because e.g. __iter__ must return the keys of the mapping.

12
  • 1
    My gut says "You can't," but my head says "You shouldn't." Commented Jul 4, 2019 at 7:43
  • notice the even the builtin python dict type iterates only one way (by keys), and provides different methods to iterate over key-value pairs, or just values. Commented Jul 4, 2019 at 7:50
  • @AdamSmith why shouldn't I? Python developers clearly thought they should, since even built-in types behave differently when using dict() or tuple() on them e.g. dd = {'a': 1, 'b': 2} and dict(dd) == {'a': 1, 'b': 2} while tuple(dd) == ('a', 'b') (or import collections; dd = collections.OrderedDict((('a', 1), ('b', 2)))) just to avoid the likely shortcut of do-nothing behavior for dict(dict). Commented Jul 4, 2019 at 8:14
  • 1
    @norok2 but that behavior is defined in the logic of the built-ins (dict and tuple). You can't change those, you can only change the type, and the type has no concept of "When I'm being converted to a tuple, do this." You can define how the object is iterated over, but not how the built-in uses that data. Commented Jul 4, 2019 at 8:24
  • In other words you can decide that you're iterating to ('a', 1) ('b', 2) and you'll get what you see, or you can decide that you're iterating to 1 2 and you'll get the tuple result you want, but not both. And importantly: even if you could do both, you shouldn't because it makes your type behave badly. Callers can't expect it to be consistent, which is Bad. Commented Jul 4, 2019 at 8:29

2 Answers 2

2

Either you declare dedicated methods:

class Something(object): def __init__(self, a, b): self.a = a self.b = b def to_dict(self): return dict(self) def to_tuple(self): return tuple((y for _, y in self)) def __iter__(self): yield ('a', self.a) yield ('b', self.b) x = Something(1, 2) print(x.to_tuple()) # (1, 2) print(x.to_dict()) # {'a': 1, 'b': 2} 

Or you modify a little the way you convert your class to a tuple:

print(tuple((y for _, y in x))) # (1, 2) print(dict(x)) # {'a': 1, 'b': 2} 

But the behaviour you would like your class to have would lead to a very tricky stituation, where the output of your __iter__ method would be different following the type you are converting this output afterward...

Sign up to request clarification or add additional context in comments.

6 Comments

"But the behaviour you would like your class to have would lead to a very tricky stituation, where tuple(dict(x)) would be different from tuple(x)" dict() why is that tricky? even with your class tuple(dict(x)) != tuple(x)...
Oh right, that was a mistake, I edited my answer (of course tuple(dict(x)) is different from tuple(x))...
And by tricky, I mean this is not the behaviour you would expect for an iterable. Your __iter__ method here behaves like the items() method of a dict, returning (key, value) tuples. This should not change according to the type you are converting your object to, right?
dict() does precisely that and we all live just fine, though
no, dict return a dict object. The dict().__iter__() method only yield the dictionnary keys, while the items method returns (key, val) tuples and the __repr__ method print a nice {k: v, ...} string.
|
0

One way can be this, but it needs explicit attribute change.

class Something(object): def __init__(self, a, b): self.a = a self.b = b def s(self, boolean): self.y = boolean return self def __iter__(self): if self.y: yield ('a', self.a) yield ('b', self.b) else: yield (self.a) yield (self.b) x = Something(1, 2) 

Outputs:-

print(tuple(x.s(0))) # (1, 2) print(dict(x.s(1))) # {'a': 1, 'b': 2} 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.