0

is there any way to add functionality to dict itself? IE - I do not want to override it - I want to create essential an extension method or mixin.

There are a log of reasons I would like to do this - but for instance - I'd like to do something like

def safe_get( the_dictionary, key ):
    return None if the_dictionary is None else the_dictionary.get(key)

# this does NOT work, because dict is a built in type
dict.safe_get = safe_get

because this is tolerable:

   result = event.safe_get("request_context").safe_get("identity").safe_get("sourceIp")

and this is ridiculous:

   result = safe_get( safe_get( safe_get( event, 'request_context'), 'identity'), 'sourceIp')

or is there another (shorter == better) way of doing the same thing?

When I google I see people defining their own classes and things - but I don't create "event" - it comes from amazon, so I have no control over it, and can't change it to another type. I could wrap it - but that would be even more code :(

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Darren Oakey
  • 2,894
  • 3
  • 29
  • 55
  • 3
    No, you can't modify built-ins. A wrapper class is not the worst idea. – Selcuk Nov 16 '21 at 05:36
  • btw, `event` usually comes as a plain dict in aws, so you could easily wrap it in a custom dict class or else use something like a dot dict access, which should be safe for nested traversal. I.e `event.context.identity.[0].source_ip`. – rv.kvetch Nov 16 '21 at 05:38
  • 1
    yeah - I know - that's what I mentioend - the question is really about - is there any way to get around that restriction - for instance in many languages they have like "monkey patching" or other such hacks.... so I'm just wondering if there's any way I can _achieve_ the same result, knowing the restrictions in place... – Darren Oakey Nov 16 '21 at 05:39
  • You can monkey-patch in Python, but not built-ins. Their implementation may not even be Python (`dict` is [implemented in C](https://github.com/python/cpython/blob/main/Objects/dictobject.c) for CPython, for example). – Selcuk Nov 16 '21 at 05:40
  • 3
    Even if you could do this, it wouldn't allow your `safe_get` to work. `self` can never be `None`, because that isn't a dictionary. – Barmar Nov 16 '21 at 05:41
  • 5
    By the way: A method which None-checks the object it is bound to doesn't make sense. If the object is None, the method call would fail with an exception anyway. – Michael Butscher Nov 16 '21 at 05:42
  • 1
    @Barmar You could have also monkey-patched `NoneType` if it was possible. – Selcuk Nov 16 '21 at 05:42
  • Good point. So you also need `NoneType.safe_get = safe_get`. – Barmar Nov 16 '21 at 05:43
  • @Selcuk True, but it doesn't seem to matter. Similar to `dict.get("key", default)` -- you can't tell if you got the default because the key was missing or that was its value. – Barmar Nov 16 '21 at 05:45
  • @Barmar True, that's why I deleted that comment. It kind of matters if you have a `safe_get` implementation for another built-in that returns something other than `None` for `None`, though. – Selcuk Nov 16 '21 at 05:46
  • Related: [Is there a Python equivalent of the C# null-coalescing operator?](https://stackoverflow.com/a/61407132/) so you could do `.get()?.get()?.get()` and each method is only called if the result of the previous one is not null. (short answer: no, but [PEP505](https://www.python.org/dev/peps/pep-0505/#pep-deferral) is about it). – TessellatingHeckler Nov 16 '21 at 05:49
  • can you override "."? like java's proxy object or similar? I thought about making a Safe class with a __getitem__ - which sort of works, but it leaves you with the wrong type at the end - eg result = Safe( event )["request_context"]["identity"]["sourceIp"] is tolerable, but leaves you holding a Safe object, not a string. If I can make a pure object proxy I could do Safe(event).request_context.identity.sourceIp – Darren Oakey Nov 16 '21 at 05:52
  • @TessellatingHeckler - yeah been through that thing - I actually spend a lot more time in C#, and I'm very used to x?.y?.z?.w - which is why not having it so painful in other languages! – Darren Oakey Nov 16 '21 at 05:54

2 Answers2

2

The question as-is can't really be answered, but there are two alternative solutions I'd propose:

def safe_get(d, *keys):
    for key in keys:
        d = d.get(key)
        if d is None:
            return None
    return d

result = safe_get(event, "request_context", "identity", "sourceIp")

Or:

class NamespaceWrapper:
    def __init__(self, wrapped: dict):
        self._wrapped = wrapped
    def __getattr__(self, attr):
        if self._wrapped is None:
            return self
        return NamespaceWrapper(self, self._wrapped.get(attr))
    def unwrap(self):
        return self._wrapped

result = NamespaceWrapper(event).request_context.identity.sourceIp.unwrap()

The latter solution could be elaborated on, for example:

  • Add a __getitem__ to access items that aren't valid identifiers;
  • Automatically unwrapping anything that isn't a dict or None;
  • Turning NamespaceWrapper into a proxy object for the wrapped dict, so things like len(w), key in w and iteration work;

... but I'm leaving that to you as an excercise.

Jasmijn
  • 9,370
  • 2
  • 29
  • 43
  • I sort of like the first alternative thanks! – Darren Oakey Nov 16 '21 at 05:58
  • For the first function, you'll want to make sure `d.get(key)` is really returning a dict, or else you will throw errors. Use `isinstance(d, dict)` to check whether d is a dict, if it isn't return `None`. – Ryno_XLI Nov 16 '21 at 06:04
0

dict.get has two arguments. The second one is optional and defaults to None. Use empty dictionary instead to achieve safity during chaining. Here's an example:

result = event.get("request_context", dict()).get("identity", dict()).get("sourceIp")