How exactly does inspect.signature work with classes? - python

The inspect.signature doc states that it supports classes as input, but it doesn't go into any sort of detail:
Accepts a wide range of Python callables, from plain functions and classes to functools.partial() objects.
If I call inspect.signature(MyClass), what signature does it return? Does it return the signature of MyClass.__init__? Or MyClass.__new__? Or something else?

It tries pretty much everything it reasonably could. I think the details are probably deliberately undocumented, because they're complicated and likely to get more so as new Python versions add more stuff to try.
For example, as of CPython 3.7.3, the code path tries the following things in order:
If the metaclass has a custom __call__ defined in Python, it uses the signature of the metaclass __call__ with the first argument removed.
Otherwise, if the class has a __new__ method defined in Python, it uses the __new__ signature with the first argument removed.
Otherwise, if the class has an __init__ method defined in Python, it uses the __init__ signature with the first argument removed.
Otherwise, it traverses the MRO looking for a __text_signature__. If it finds one, it parses __text_signature__ to get the signature information.
If it still hasn't found anything, if the type's __init__ is object.__init__ and the type's __new__ is object.__new__, it returns the signature of the object class. (There's a misleading comment and a possible bug involving metaclasses around this point - the comment says it's going to check for type.__init__, but it doesn't do that. I think this commit may have made a mistake here.)
If it still hasn't found anything, it gives up and raises a ValueError saying it couldn't find anything.

Related

Why method accepts class name and name 'object' as an argument?

Consider the following code, I expected it to generate error. But it worked. mydef1(self) should only be invoked with instance of MyClass1 as an argument, but it is accepting MyClass1 as well as rather vague object as instance.
Can someone explain why mydef is accepting class name(MyClass1) and object as argument?
class MyClass1:
def mydef1(self):
return "Hello"
print(MyClass1.mydef1(MyClass1))
print(MyClass1.mydef1(object))
Output
Hello
Hello
There are several parts to the answer to your question because your question signals confusion about a few different aspects of Python.
First, type names are not special in Python. They're just another variable. You can even do something like object = 5 and cause all kinds of confusion.
Secondly, the self parameter is just that, a parameter. When you say MyClass1.mydef1 you're asking for the value of the variable with the name mydef1 inside the variable (that's a module, or class, or something else that defines the __getattr__ method) MyClass1. You get back a function that takes one argument.
If you had done this:
aVar = MyClass1()
aVar.mydef1(object)
it would've failed. When Python gets a method from an instance of a class, the instance's __getattr__ method has special magic to bind the first argument to the same object the method was retrieved from. It then returns the bound method, which now takes one less argument.
I would recommend fiddling around in the interpreter and type in your MyClass1 definition, then type in MyClass1.mydef1 and aVar = MyClass1(); aVar.mydef1 and observe the difference in the results.
If you come from a language like C++ or Java, this can all seem very confusing. But, it's actually a very regular and logical structure. Everything works the same way.
Also, as people have pointed out, names have no type associated with them. The type is associated with the object the name references. So any name can reference any kind of thing. This is also referred to as 'dynamic typing'. Python is dynamically typed in another way as well. You can actually mess around with the internal structure of something and change the type of an object as well. This is fairly deep magic, and I wouldn't suggest doing it until you know what you're doing. And even then you shouldn't do it as it will just confuse everybody else.
Python is dynamically typed, so it doesn't care what gets passed. It only cares that the single required parameter gets an argument as a value. Once inside the function, you never use self, so it doesn't matter what the argument was; you can't misuse what you don't use in the first place.
This question only arises because you are taking the uncommon action of running an instance method as an unbound method with an explicit argument, rather than invoking it on an instance of the class and letting the Python runtime system take care of passing that instance as the first argument to mydef1: MyClass().mydef1() == MyClass.mydef1(MyClass()).
Python is not a statically-typed language, so you can pass to any function any objects of any data types as long as you pass in the right number of parameters, and the self argument in a class method is no different from arguments in any other function.
There is no problem with that whatsoever - self is an object like any other and may be used in any context where object of its type/behavior would be welcome.
Python - Is it okay to pass self to an external function

Trying to eliminate the types module in python code

Is saying:
if not callable(output.write):
raise ValueError("Output class must have a write() method")
The same as saying:
if type(output.write) != types.MethodType:
raise exceptions.ValueError("Output class must have a write() method")
I would rather not use the types module if I can avoid it.
No, they are not the same.
callable(output.write) just checks whether output.write is callable. Things that are callable include:
Bound method objects (whose type is types.MethodType).
Plain-old functions (whose type is types.FunctionType)
partial instances wrapping bound method objects (whose type is functools.partial)
Instances of you own custom callable class with a __call__ method that are designed to be indistinguishable from bound method objects (whose type is your class).
Instances of a subclass of the bound method type (whose type is that subclass).
…
type(output.write) == types.MethodType accepts only the first of these. Nothing else, not even subclasses of MethodType, will pass. (If you want to allow subclasses, use isinstance(output.write, types.MethodType).)
The former is almost certainly what you want. If I've monkeypatched an object to replace the write method with something that acts just like a write method when called, but isn't implemented as a bound method, why would your code want to reject my object?
As for your side question in the comments:
I do want to know if the exceptions.ValueError is necessary
No, it's not.
In Python 2.7, the builtin exceptions are also available in the exceptions module:
>>> ValueError is exceptions.ValueError
True
In Python 3, they were moved to builtins along with all the other builtins:
>>> ValueError is builtins.ValueError
True
But either way, the only reason you'd ever need to refer to its module is if you hid ValueError with a global of the same name in your own module.
One last thing:
As user2357112 points out in a comment, your solution doesn't really ensures anything useful.
The most common problem is almost certainly going to be output.write not existing at all. In which case you're going to get an AttributeError rather than the ValueError you wanted. (If this is acceptable, you don't need to check anything—just call the method and you'll get an AttributeError if it doesn't exist, and a TypeError if it does but isn't callable.) You could solve that by using getattr(output, 'write', None) instead of output.write, because None is not callable.
The next most common problem is probably going to be output.write existing, and being callable, but with the wrong signature. Which means you'll still get the same TypeError you were trying to avoid when you try to call it. You could solve that by, e.g., using the inspect module.
But if you really want to do all of this, you should probably be factoring it all out into an ABC. ABCs only have built-in support for checking that abstract methods exist as attributes; it doesn't check whether they're callable, or callable with the right signature. But it's not that hard to extend that support. (Or, maybe better, just grabbing one of the interface/protocol modules off PyPI.) And I think something like isinstance(output, StringWriteable) would declare your intention a lot better than a bunch of lines involving getattr or hasattr, type checking, and inspect grubbing.

Does the Python interpreter bind instances to methods or the self parameter?

I am reading a book about Object-Oriented Programming in Python. There is a sentence that I am confused by:
The interpreter automatically binds the instance upon which the method is invoked to the self parameter.
In this sentence what is bound to the instance. the method, or the self parameter?
This is actually not such a bad question and I'm not sure why it got downvoted so quickly...
Even though Python supports object-oriented, I find it to be much closer to functional-programming languages, one of the reasons for that is that functions are invoked "on" objects, not "by" them.
For example: len(obj) where in a "true" object oriented programing language you'd expect to be able to do something like obj.length()
In regards to the self parameter, you're calling obj.method(other_args) but what really happens under the hood is a translation of this call to: method(obj, other_args) you can see that when the method is declared you're doing it with the self variable passed in as the first argument:
class ...
def method(self, other_args):
...
so it's basically all about the "translation" of obj.method(other_args) to method(obj, other_args)

Python Pickle call constructor

I'd like to provide defaults for missing values using Python's pickle serialiser. Since the classes are simple, the defaults are naturally present in the classes's __init__ methods.
I can see from pickle documentation that there is __getnewargs__. However, this only works for cases where __getnewargs__ was present prior to "pickling".
Is there any way to tell python pickle to call always the constructor rather than starting with an uninitialised object?
Unpickling will always create an instance without calling __init__(). This is by design. In python 2 it was possible to override __getinitargs__() to cause unpickling to call __init__() with some arguments, but it was necessary to have had this method overridden at pickling time. This is not available in python 3 anymore.
To achieve what you want, wouldn't it be enough to just manually call self.__init__() from self.__setstate__(state)? You can provide any default arguments not found in state.

Calling methods surrounded by __ in Python

I'm reading a book on Python, and it says that when you make a call to help(obj) to list all of the methods that can be called on obj, the methods that are surrounded by __ on both sides are private helper methods that cannot be called.
However, one of the listed methods for a string is __len__ and you can verify that if s is some string, entering s.__len__() into Python returns the length of s.
Why is okay to call some of these methods, such as __len__, but others cannot be called?
The book is incorrect. You can call __dunder__ special methods directly; all that is special about them is their documented use in Python and how the language itself uses them.
Most code just should not call them directly and leave it to Python to call them. Use the len() function rather than call the __len__ method on the object, for example, because len() will validate the __len__ return value.
The language reserves all such names for its own use; see Reserved classes of identifiers in the reference documentation:
System-defined names, informally known as "dunder" names. These names are defined by the interpreter and its implementation (including the standard library). Current system names are discussed in the Special method names section and elsewhere. More will likely be defined in future versions of Python. Any use of __*__ names, in any context, that does not follow explicitly documented use, is subject to breakage without warning.

Categories

Resources