Why does a built-in static method have a `staticmethod` wrapper?

… when a built-in __new__ does not?

As we all know …

  • In a class defined in Python, methods we wish to be static are explicitly decorated @staticmethod, and as a special case, __new__ is given one implicitly. And this is how they enter the dictionary of a type.
  • The wrapper is needed because the underlying function that results from def has binding behaviour (__get__) that we have to disable. staticmethod.__get__ simply returns the wrapped function, and so the result of descriptor binding is to return that.

In a built-in type, the result of processing a PyMethodDef with METH_STATIC flag (of which there are not many uses in the stdlib) is at first an unbound builtin_function_or_method, then a staticmethod wrapper is added:

    >>> type(str.__dict__['maketrans'])
    <class 'staticmethod'>
    >>> type(str.__dict__['maketrans'].__func__)
    <class 'builtin_function_or_method'>
    >>> type(str.__dict__['maketrans'].__func__.__self__)
    <class 'NoneType'>

The same experiment with __new__ shows that it has no staticmethod wrapper and is bound to the type:

    >>> type(str.__dict__['__new__'])
    <class 'builtin_function_or_method'>
    >>> str.__dict__['__new__'].__self__
    <class 'str'>

builtin_function_or_method (bound or not) has no binding __get__ to disable, and so this works just fine.

Could we not, in the same way, place the unbound builtin_function_or_method in the dictionary directly?

Edit: sorry for making this hop around categories. Right place now I think.

I"m a bit confused about what you expect, but __new__ is not a staticmethod – it is a classmethod.

In [15]: class C:
    ...:     def __new__(cls):
    ...:         print(cls)
    ...: 

In [16]: C()
<class '__main__.C'>

But very much a special case anyway :slight_smile:

As implemented currently, __new__ defined on a dynamic class is wrapped by a staticmethod descriptor. From the docs:

Called to create a new instance of class cls. __new__() is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument.

class A:
   def __new__(cls):
       return super().__new__(cls)
>>> isinstance(vars(A)['__new__'], staticmethod)
True
>>> isinstance(A.__new__, types.FunctionType)
True

As shown above, when __new__() is called directly, the class has to be passed explicitly as the first argument. If __new__ were set as a classmethod descriptor, then accessing it as an attribute of a class or instance would create a bound method object.

3 Likes

__new__ needs a class as the first argument, but not is not automatically supplied with the class during binding. In your example, try asking type(C.__dict__['__new__']).

However, the question is about built-ins and why other static methods can’t be exposed more like __new__.


Footnote: You’re not the first to this misunderstanding:

Factoid: __new__ is a static method, not a class method. I initially thought it would have to be a class method, and that’s why I added the classmethod primitive. Unfortunately, with class methods, upcalls don’t work right in this case, so I had to make it a static method with an explicit class as its first argument.

2 Likes

With that misunderstanding cleared up, does anyone have an answer to the question?

Here’s an experiment. str.maketrans is a METH_STATIC built-in, as we can verify:

>>> str.__dict__['maketrans']
<staticmethod(<built-in method maketrans of type object at 0x00007FFA78A49470>)>
>>> str.__dict__['maketrans'].__func__
<built-in method maketrans of type object at 0x00007FFA78A49470>

I can place the naked built-in function in the dictionary of a type like this:

>>> class S(str): pass
... 
>>> S.maketrans = str.__dict__['maketrans'].__func__
>>> S.__dict__['maketrans']
<built-in method maketrans of type object at 0x00007FFA78A49470>

Calling it seems to work just fine:

>>> str.maketrans("abcd", "ABCD")
{97: 65, 98: 66, 99: 67, 100: 68}
>>> S.maketrans("abcd", "ABCD")
{97: 65, 98: 66, 99: 67, 100: 68}

So why does Python wrap it in a staticmethod when building str?