2

I have the following Python code:

myArray = [{ "id": 1, "desc": "foo", "specs": { "width": 1, "height": 1}}, { "id": 2, "desc": "bar", "specs": { "width": 2, "height": 2, "color": "black"}}, { "id": 3, "desc": "foobar"}]
print len(myArray)

myArray_filtered = filter(lambda item : hasattr(item, "specs") and hasattr(item.specs, "color"), myArray)
print len(myArray_filtered)

I expect to get length 1 on second print, but it is 0. Can you tell me what's wrong with my code?

smartmouse
  • 13,912
  • 34
  • 100
  • 166
  • Possible duplicate of [Python's hasattr on list values of dictionaries always returns false?](https://stackoverflow.com/questions/10724766/pythons-hasattr-on-list-values-of-dictionaries-always-returns-false); i.e. use `in` instead. – meowgoesthedog Mar 04 '19 at 16:31
  • If you split your conditions, you'll find your first filter command returns an empty list -- length 0, any further filtering will, similarly return length 0. – hd1 Mar 04 '19 at 16:37
  • @meowgoesthedog I tried to use `myArray_filtered = filter(lambda item : 'specs' in item and 'color' in item.specs), myArray)` but it doesn't work. Sorry this is my first time with Python! – smartmouse Mar 04 '19 at 16:39
  • @hd1 Yes, you are right, I see it now... anyway I can't figure out what is wrong. – smartmouse Mar 04 '19 at 16:40
  • @meowgoesthedog your answer has an advantage over mine that if the value of "color" is `None` i would need to use another default – Chris_Rands Mar 04 '19 at 16:46
  • dictionary items are accessed via [subscription](https://docs.python.org/3/reference/expressions.html?highlight=subscription#subscriptions) (e.g., `d['key']`) - they are not [attributes](https://docs.python.org/3/reference/expressions.html?highlight=subscription#attribute-references). – wwii Mar 04 '19 at 16:49
  • @Chris_Rands your approach would be more succinct for a more deeply nested structure. – meowgoesthedog Mar 04 '19 at 16:51

2 Answers2

3

Given your nested structure, you could use dict.get with some default values:

>>> myArray_filtered = list(filter(lambda d: d.get("specs", {}).get("color") is not None, myArray))
>>> len(myArray_filtered)
1
>>> myArray_filtered
[{'id': 2, 'desc': 'bar', 'specs': {'width': 2, 'height': 2, 'color': 'black'}}]
Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
  • @smartmouse You only need the `list()` call in Python 3, but you should really not be learning Python 2 anyway at this stage – Chris_Rands Mar 04 '19 at 16:43
  • I'm on Python 2.7.x and it works without `list()`. Thank you. – smartmouse Mar 04 '19 at 16:45
  • Do you know how to add more condition? For example if I want to check even `width == 2` ? – smartmouse Mar 04 '19 at 16:46
  • @Chris_Rands Very good solution, I have the same problem that you have solved but instead of the specs being an object it is a list [] How would the same example be with the specs in [] list? – Juanperez Nov 01 '20 at 13:00
1
myArray_filtered = [v for v in myArray if v.get('specs', {}).get('color')]
print(len(myArray_filtered))

Slightly simpler just using list comprehensions.

And you can add to the condition:

myArray_filtered = [v for v in myArray if v.get('specs', {}).get('color') and v.get('specs', {}).get('width') == 2]
print(len(myArray_filtered))
Caspar Wylie
  • 2,818
  • 3
  • 18
  • 32