2

Say I have function f as follows:

def f(c=None):
    return 42 if c is None else c

Then I can't get None out of this function. Now you could think "well, just check for another value like 2128.213 or whatever" but then I can't get that specific value out of the function can I?

That's why I'd like to distinguish, if possible, between f() and f(None) so that I can have

f() -> 42
f(None)-> None

Bear in mind this is a simplified example. In practice it's a class's __init__(...) function with multiple positional arguments which I'd like to handle as c in this example.

martineau
  • 119,623
  • 25
  • 170
  • 301
ted
  • 13,596
  • 9
  • 65
  • 107

2 Answers2

3

The common practice in such cases is to use specific sentinel value you never want to return.

class Sentinel():
    pass


_sentinel = Sentinel()

# _sentinel = object()  # this is the option too

def f(x=_sentinel):
    return 42 if x is _sentinel else x

assert f() == 42
assert f(None) is None
assert f(5) == 5

ted
  • 13,596
  • 9
  • 65
  • 107
NobbyNobbs
  • 1,341
  • 1
  • 11
  • 17
  • @wjandrea `somemodule.f(somemodule._sentinel) == somemodule._sentinel`, that's the problem – TheEagle Apr 21 '21 at 19:59
  • 3
    @Programmer No one should use `somemodule._sentinel` because the leading underscore indicates that it's for internal use only. It's still possible of course, since Python doesn't have truly private variables, but it's strongly discouraged. – wjandrea Apr 21 '21 at 20:02
  • @wjandrea : please see this comment and what they are discussing "Use a a default value something like _DEFAULT = object(), defined in the same module - no other module can possibly pass a value that's identical to that. – jasonharper" – Diwakar SHARMA Apr 21 '21 at 20:02
  • Minor point: `assert f(None) == None` should be `assert f(None) is None` – wjandrea Apr 21 '21 at 20:04
  • @wjandrea "since Python doesn't have truly private variables" - it has, put `class cls: __attr = 1` in a py file, import it and try `print(somemodule.cls.__attr)` ... – TheEagle Apr 21 '21 at 20:05
  • @Diwakar Sorry, I'm not sure I understand what you're saying. – wjandrea Apr 21 '21 at 20:06
  • 3
    @Programmer That's still not truly private, just obfuscated. See [this answer](https://stackoverflow.com/a/32802486/4518341) for an explanation. TL;DR `cls._cls__attr` is the obfuscated name. – wjandrea Apr 21 '21 at 20:09
  • 1
    @Programmer never heard about name mangling? – NobbyNobbs Apr 21 '21 at 20:10
  • @NobbyNobbs obviously not, may you share a link ? – TheEagle Apr 21 '21 at 20:10
  • @wjandrea but it's as if it were private because you cannot access it – TheEagle Apr 21 '21 at 20:11
  • 3
    @Programmer You **can** access it, using `cls._cls__attr`. See the answer I linked for an explanation, as well as [What is the meaning of single and double underscore before an object name?](https://stackoverflow.com/q/1301346/4518341) – wjandrea Apr 21 '21 at 20:12
  • @wjandrea thanks, I didn't know that, learned something new ! – TheEagle Apr 21 '21 at 20:17
2

You can make use of Python's private attributes : Assign a new object to that private attribute, compare if the argument is the private object, and done.

class somename:
    __default = object()
    def __init__(self, default=__default):
        print(42 if default is self.__default else default)

somename()
somename(default=12)
TheEagle
  • 5,808
  • 3
  • 11
  • 39