name and age aren't packed into the first kwargs in func_print() therefore aren't passed further into other functions. Or in other words, it's not collecting all arguments, it's only collecting arguments that are unspecified, "dangling", so that it doesn't throw an error. Similar to Varargs in C.
None comes from the func_print()'s return as it's the default value of any function.
For example:
def func():
... # check help(...) and type(...)
# implicit "return None"
def func():
pass
# implicit "return None"
def func():
return
# implicit "return None"
def func():
# explicit return None
return None
What you can however do is something that's common in JavaScript nowadays, send an object as an argument. Fortunately, you don't need to create and pass a custom object instance or anything like that because just **kwargs itself is a syntax that creates a dictionary (alternatively *args creates a tuple), so an object's instance preserving the arguments. (Similarly, you can use *args for positional arguments, more on that here).
From that you can do this:
def other(**kwargs):
print(kwargs)
def func(**kwargs):
print(kwargs.get("name", "lolo"))
other(**kwargs)
print(func(name=123, hello="world"))
# 123
# {'name': 123, 'hello': 'world'}
# None
print(func(hello="world"))
# lolo
# {'hello': 'world'}
# None