Skip to content

False positive on guarded attribute access. #7895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
amcgregor opened this issue Nov 6, 2019 · 3 comments
Closed

False positive on guarded attribute access. #7895

amcgregor opened this issue Nov 6, 2019 · 3 comments

Comments

@amcgregor
Copy link

Howdy! Ran into a possible bug or aspect lacking coverage relating to protections against unintentional (/unaware) bad attribute access.


  • Python 3.7.5
  • mypy 0.740
  • mypy-extensions 0.4.3
  • pytest-mypy 0.4.2
  • typeguard 2.3.1
[mypy]
follow_imports = silent
ignore_missing_imports = True
strict_optional = True
warn_no_return = False
check_untyped_defs = True
allow_redefinition = True

I have a small utility to exhaustively explore object attributes for comparison of the differences between function objects, decorated function objects, nested decorated function objects, bound and unbound instance methods, bound and unbound class methods, and static methods, to explore runtime and runtime version compatibility and changes to identifying marks.

The test setup is a decorator, various decorated functions, and a class declaration with most possibilities included:

def simple_decorator(fn):
	@wraps(fn)
	def inner(*args, **kw):
		return fn(*args, **kw)
	
	return inner


def bare():
	def closure():
		pass
	
	return closure


@simple_decorator
def decorated_shallow():
	pass


@simple_decorator
@simple_decorator
def decorated_deep():
	pass


class Example:
	class Pandora:
		class Box:
			def nested(self):
				pass
		
		def nested(self):
			pass
	
	def instance(self):
		return self
	
	@classmethod
	def classmethod(cls):
		return cls
	
	@staticmethod
	def staticmethod():
		pass
	
	@simple_decorator
	def decorated_shallow(self):
		pass
	
	@simple_decorator
	@simple_decorator
	def decorated_deep(self):
		pass

instance = Example()

I then have a small amount of (rather deeply nested closure) code to iterate these and identify which are re-discoverable either through direct comparison or through presence of __name__ and self-identification of that name within their REPR output:

for i in (Example, Example.Pandora, Example.Pandora.Box, Example.Pandora.Box.nested, Example.Pandora.nested, Example.instance, Example.classmethod, Example.staticmethod, instance.instance, instance.classmethod, instance.staticmethod, instance):
	if obj is i or obj == i: return True  # Can be safely rediscovered through comparison.
	if hasattr(i, '__name__') and i.__name__ in repr(obj): return True  # Better yet, explicit name.

The above results in:

109: error: "object" has no attribute "__name__"; maybe "__ne__" or "__new__"?

I am somewhat aghast. __name__ isn't universal, but it's extremely common for any scoped object (within a module, class, etc.), and this particular use is guarded in such a way that the attribute access would not happen if missing.

I've pivoted this away from two-component evaluation and towards getattr w/ default, which resolves the complaint. But this should not be a complaint. ;)

@asottile
Copy link
Contributor

asottile commented Nov 7, 2019

if you use isinstance(i, type) and i.__name__ ... mypy will probably know what's up -- as far as I know the current narrowing for "instance has attribute" is only through type narrowing (either through isinstance or in rare cases through sys.version_info)

@asottile
Copy link
Contributor

asottile commented Nov 7, 2019

oh right but that doesn't directly work for methods / functions / other Callables :(

@ilevkivskyi
Copy link
Member

Duplicate of #1424

@ilevkivskyi ilevkivskyi marked this as a duplicate of #1424 Nov 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants