To restate the motivation: the hope is that there is a potentially large benefit of being able to more easily refactor code with something like a nameof().
I am going to make the claim that: 1. this benefit is actually minimal and does not address a lot of other existing refactoring problems/chores, and 2. comes at the cost of a possible loss of readability, and therefore isn't worth the trade Consider the following typical code (I hope it is nice; I feel like I can certainly read it): import logging import functools class FunctionLogger: """Records use of a function. The optional `log` parameter is to be a callable that accepts a string. It is logging.info by default. """ log = logging.info def __init__(self, log=None): if log is not None: self.log = log def __call__(self, func): func_name = func.__name__ @functools.wraps(func) def wrapper(*args, **kwargs): try: self.log(f"called {func_name!r}") except: cls_name = type(self).__name__ logging.exception(f"failed to log {func_name!r} call; is {cls_name}.log set correctly?") finally: return func(*args, **kwargs) return wrapper Let's think through: what refactoring must occur to change the "log" parameter to something else? And, how readable does the code remain afterward? At least some of the code could certainly benefit from being more easily refactored (by an IDE) with nameof(), but how much? import logging import functools class FunctionLogger: # NOTE: this docstring will result in a runtime error, so can't even use nameof() here to help ourselves f"""Records use of a function. The optional `{nameof(FunctionLogger.log)}` parameter is to be a callable that accepts a string. It is logging.info by default. """ log = logging.info # no need for nameof() here, but see below * def __init__(self, log=None): # IDE will refactor the init signature SEPARATELY (see below *) if log is not None: # no help from nameof() in __init__ body # IDE will get this one just fine anyway; shouldn't use nameof() in this situation # using it actually hurts readability setattr(self, nameof(self.log), log) def __call__(self, func): func_name = nameof(func) # it's different, but NOT an improvement over func.__name__ @functools.wraps(func) def wrapper(*args, **kwargs): try: self.log(f"called {func_name!r}") except: cls_name = nameof(type(self)) # different, but NOT an improvement over type(self).__name__ log_param_name = nameof(self.log) # ok, HERE is a place we are definitely going to benefit from nameof() # readability of next line might actually be slightly improved...? But perhaps it's also worse...? # but the IDE will refactor next line automatically, which is handy. logging.exception(f"failed to log {func_name!r} call; is {cls_name}.{log_param_name} set correctly?") finally: return func(*args, **kwargs) return wrapper * For the class level member and init signature/body: the IDE won't know to include the class member and the init signature/body in a refactor of a member-level variable, and this problem isn't helped by nameof(). Multiple refactoring chores have to be completed to change the parameter name: 1. the init signature, 2. the class level variable, 3. object level variable, 4. the class docstring, 5. all of the f strings. Using a nameof() only totally prevents one of them (the f strings; it can't help with the docstring). So I guess there is SOME benefit here. But I think it comes at the cost of some potential loss of readability, and there are some major places in which nameof() doesn't bring any help at all (looking at you, class docstring). And there is a ready alternative: use a global if you think you might rename your parameters later (see code at bottom). This rolls TWO of the refactoring chores into one (the f strings and the docstring). *Bottom line: refactoring remains a chore. The win is minimal. Do nothing option is preferred.* import logging import functools _FOO = "foo" # haven't decided on .foo member name yet... class FunctionLogger: # with a global, this docstring will work! f"""Records use of a function. The optional `{_FOO}` parameter is to be a callable that accepts a string. It is logging.info by default. """ log = logging.info def __init__(self, log=None): if log is not None: self.log = log def __call__(self, func): func_name = func.__name__ @functools.wraps(func) def wrapper(*args, **kwargs): try: self.log(f"called {func_name!r}") except: cls_name = type(self).__name__ # employ the global below for the error message, mischief managed logging.exception(f"failed to log {func_name!r} call; is {cls_name}.{_FOO} set correctly?") finally: return func(*args, **kwargs) return wrapper --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/JOUJOABM5KX2VJZMBPCXTUFLYZXBVYXB/ Code of Conduct: http://python.org/psf/codeofconduct/