A simple way that works for most cases is to just set the default value to None. You can compare against None to see if a caller has overridden it. You couldn't tell if a caller didn't supply it or if they supplied None, but it's rare that such a case matters.
def func1(required="foo", opt1=None):
if opt1 == None:
print(f"opt1 was either passed in as None, or wasn't passed in at all")
else:
print(f"opt1 was set to {opt1}")
func1()
func1(opt1=None)
func1(opt1="text")
Output:
opt1 was either passed in as None, or wasn't passed in at all
opt1 was either passed in as None, or wasn't passed in at all
opt1 was set to text
If that really matters to you, or if there are a lot of optional arguments, you can just read them in as
**kwargs
to capture all the additional key/value pairs.
def func2(required="foo", **kwargs):
if "opt1" not in kwargs:
print(f"opt1 wasn't passed in at all.")
else:
print(f"opt1 was passed in. It was set to {kwargs['opt1']}")
func2()
func2(opt1=None)
func2(opt1="text")
Output:
opt1 wasn't passed in at all.
opt1 was passed in. It was set to None
opt1 was passed in. It was set to text