There's a concept that's sometimes useful when explaining behavior of certain Python operations in terms of simpler ones, and it is "does the class of x define a method m?". This currently cannot expressed using hasattr(): hasattr(x, 'm') might find an instance variable named m, but that is not a method (and there are contexts where they are not interchangeable); hasattr(type(x), 'm') might find a *metaclass* method. Example of the former (assume Python 3): class C: def __add__(self, other): return 42 c = C() c.__add__ = lambda *args: 0 c + 1 # prints 42, not 0 Example of the latter: class C: pass c = C() hasattr(C, 'mro') # prints True, since mro() is a method of the standard metaclass ('type'). c.mro() # raises AttributeError The use case I am mainly thinking of is the formal explanation of the semantics of binary (and other) operators, e.g. def __add__(a, b): r = NotImplemented if hasmethod(a, '__add__'): r = a.__add__(b) if r is NotImplemented and hasmethod(b, '__radd__'): r = b.__radd__(a) if r is NotImplemented: raise TypeError return r (Caveat: it's even more complicated if type(b) is a subclass of type(a).) I'm not sure if it would be better if the first argument of hasmethod() was a type instead of an instance (so the example would use hasattr(type(a), '__add__') etc.). It's also interesting to figure out what should happen for proxy types. (Proxy types in general need a better foundation.) Thoughts? -- --Guido van Rossum (python.org/~guido)
On Wed, Aug 29, 2012 at 03:48:01PM -0700, Guido van Rossum wrote:
There's a concept that's sometimes useful when explaining behavior of certain Python operations in terms of simpler ones, and it is "does the class of x define a method m?".
This currently cannot expressed using hasattr(): hasattr(x, 'm') might find an instance variable named m, but that is not a method (and there are contexts where they are not interchangeable); hasattr(type(x), 'm') might find a *metaclass* method.
Example of the former (assume Python 3):
class C: def __add__(self, other): return 42
c = C() c.__add__ = lambda *args: 0 c + 1 # prints 42, not 0
Example of the latter:
class C: pass c = C() hasattr(C, 'mro') # prints True, since mro() is a method of the standard metaclass ('type'). c.mro() # raises AttributeError
The use case I am mainly thinking of is the formal explanation of the semantics of binary (and other) operators, e.g.
def __add__(a, b): r = NotImplemented if hasmethod(a, '__add__'): r = a.__add__(b) if r is NotImplemented and hasmethod(b, '__radd__'): r = b.__radd__(a) if r is NotImplemented: raise TypeError return r
I'd usually simply use ``hasattr(a, "__add__")``. This is also what e.g. MutableMapping.update() currently does. The chances that someone accidentally passes in an object that has a callable instance variable called "__add__" seem pretty low, and we don't need to protect against people intentionally trying to break things. That said, ``hasmethod()`` can be implemented in a quite straight-forward way in pure Python: def hasmethod(obj, name): return inspect.ismethod(getattr(obj, name, None)) Cheers, Sven
On Thu, Aug 30, 2012 at 9:13 AM, Sven Marnach <sven@marnach.net> wrote:
I'd usually simply use ``hasattr(a, "__add__")``. This is also what e.g. MutableMapping.update() currently does. The chances that someone accidentally passes in an object that has a callable instance variable called "__add__" seem pretty low, and we don't need to protect against people intentionally trying to break things.
That said, ``hasmethod()`` can be implemented in a quite straight-forward way in pure Python:
def hasmethod(obj, name): return inspect.ismethod(getattr(obj, name, None))
I don't think that's *quite* what Guido is getting at: it's more about exposing the semantics of _PyType_Lookup at the Python level. There are currently two different ways of approximating that. The one which mimics _PyType_Lookup most closely (by treating proxy types as instances of the proxy type, rather than the target type) is to do "type(x.).attr", "getattr(type(x), 'attr')" or "hasattr(type(x), 'attr')" rather than performing those operations directly on 'x'. There's an alternative which treats proxy objects as an instance of the *target* type, which is to respect the claimed value of __class__: "x.__class__.attr", "getattr(x.__class__, 'attr')" or "hasattr(x.__class__, 'attr')" Yet *both* of those alternatives have the problem Guido noted, where they can find metaclass methods that the descriptor protocol would ignore:
class C: pass ... c = C() type(c) <class '__main__.C'> c.__class__ <class '__main__.C'> hasattr(c, "mro") False hasattr(type(c), "mro") True
_PyType_Lookup is essentially "ordinary attribute lookup, but ignore the instance variables and don't supply the instance to descriptors", or, equivalently, "class attribute lookup, but don't fallback to the metaclass". It's a necessary part of the language semantics, but it's not currently accessible from Python code. I believe the last time this came up, the main idea being kicked around was a new function in the operator module (e.g. "operator.gettypeattr()") rather than a new builtin. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Wed, Aug 29, 2012 at 03:48:01PM -0700, Guido van Rossum wrote:
There's a concept that's sometimes useful when explaining behavior of certain Python operations in terms of simpler ones, and it is "does the class of x define a method m?".
It's not just methods where this is useful. For example, the help() quasi-builtin ignores instance attribute x.__doc__ and instead uses type(x).__doc__. I'm not sure that needing this is common enough to justify builtins, but I think it would be useful to have hastypeattr and friends (get*, set* and del*) in the operator module. -- Steven
On Wed, Aug 29, 2012 at 9:28 PM, Steven D'Aprano <steve@pearwood.info>wrote:
On Wed, Aug 29, 2012 at 03:48:01PM -0700, Guido van Rossum wrote:
There's a concept that's sometimes useful when explaining behavior of certain Python operations in terms of simpler ones, and it is "does the class of x define a method m?".
It's not just methods where this is useful. For example, the help() quasi-builtin ignores instance attribute x.__doc__ and instead uses type(x).__doc__.
I'm not sure that needing this is common enough to justify builtins, but I think it would be useful to have hastypeattr and friends (get*, set* and del*) in the operator module.
+1. I wouldn't call it hasmethod as described either as that name implies to most readers the much simpler "it has a callable attribute with this name" check rather than getting into the differences between an arbitrary callable attribute, function, or an actual method on the type which most people do not need to know about (*). -gps (*) unless mocking, i ran into a test failure because for 2.6 it was stubbing out a __special__ method with a mock no longer worked in 2.7. but that test was better all around when refactored to not need to be that crazy. :)
participants (5)
-
Gregory P. Smith
-
Guido van Rossum
-
Nick Coghlan
-
Steven D'Aprano
-
Sven Marnach