jia Jimmy
jia Jimmy

Reputation: 1848

Why staticmethod decorator not needed?

I'm trying to use a class decorator to achieve a singleton pattern as below:

python3.6+


def single_class(cls):

    cls._instance = None
    origin_new = cls.__new__

    # @staticmethod
    # why staticmethod decorator is not needed here?
    def new_(cls, *args, **kwargs):
        if cls._instance:
            return cls._instance
        cls._instance = cv = origin_new(cls)
        return cv

    cls.__new__ = new_

    return cls


@single_class
class A():
    ...


a = A()
b = A()

print(a is b ) # True

The singleton pattern seems to be working well, but I'm wondering why @staticmethod is not needed above the function new_ in my code, as I know that cls.__new__ is a static method.


class object:
    """ The most base type """
    ...
    @staticmethod # known case of __new__
    def __new__(cls, *more): # known special case of object.__new__
        """ Create and return a new object.  See help(type) for accurate signature. """
        pass
    ...

Update test with python2.7+

The @staticmethod seems to be needed in py2 and not needed in py3


def single_class(cls):

    cls._instance = None
    origin_new = cls.__new__

    # @staticmethod
    # without @staticmethod there will be a TypeError 
    # and work fine with @staticmethod adding
    def new_(cls, *args, **kwargs):
        if cls._instance:
            return cls._instance
        cls._instance = cv = origin_new(cls)
        return cv

    cls.__new__ = new_

    return cls


@single_class
class A(object):
    pass


a = A()
b = A()

print(a is b ) 

# TypeError: unbound method new_() must be called with A instance as the first argument (got type instance instead)

Upvotes: 3

Views: 382

Answers (3)

Amit Tripathi
Amit Tripathi

Reputation: 7261

__new__ explicitly takes the class instance as its first argument. __new__, as mentioned in other answers, is a special case and a possible reason for it to be staticmethod is to allow the creation of other classes using new:

super(CurrentClass, cls).__new__(otherCls, *args, **kwargs)

The reason why your code works without @staticmethod decorator in Python 3 but doesn't work in Python 2 is because of the difference in how Python 2 and Python 3 allow a class's method access.

There is no unbounded method in Python 3 [ 2 ]. When you try to access a class method on Python 3 you get a function whereas in Python 2 you get unbounded method. You can see this if you do:

# Python 2
>>> A.__new__
<unbound method A.new_>

# Python 3
>>> A.__new__
<function __main__.single_class.<locals>.new_(cls, *args, **kwargs)>

In Python 2, your decorator is equal to single_class.__new__(A) but since __new__ is an unbound method you can't call it with the class itself. You need a class instance but for that, you need to create your class(catch-22) and that's why staticmethod is needed. The error message says the same thing unbound method new_() must be called with A instance as first argument.

Whereas in Python 3 __new__ is treated as a function you can call it with class A itself. So, single_class.__new__(A) will work.


Upvotes: 2

bruno desthuilliers
bruno desthuilliers

Reputation: 77902

It's not needed for this special method because that's the official spec (docs.python.org/3/reference/datamodel.html#object.new). Quite simply.

EDIT:

The @staticmethod seems to be needed in py2

It's not:

bruno@bruno:~$ python2
Python 2.7.17 (default, Nov  7 2019, 10:07:09) 
[GCC 7.4.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
pythonrc start
pythonrc done
>>> class Foo(object):
...     def __new__(cls, *args, **kw):
...         print("hello %s" % cls)
...         return object.__new__(cls, *args, **kw)
... 
>>> f = Foo()
hello <class '__main__.Foo'>

but your example is quite a corner case since you're rebinding this method after the class has been created, and then it stop working in py2 indeed:

>>> class Bar(object):
...     pass
... 
>>> def new(cls, *args, **kw):
...     print("yadda %s" % cls)
...     return object.__new__(cls, *args, **kw)
... 
>>> Bar.__new__ = new
>>> Bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method new() must be called with Bar instance as first argument (got type instance instead)

I assume that in py2, __new__ (if present) is special-cased by the metaclass constructor (type.__new__ / type.__init__) which wraps it in a staticmethod:

>>> Foo.__dict__["__new__"]
<staticmethod object at 0x7fe11e15af50>
>>> Bar.__dict__["__new__"]
<function new at 0x7fe11e12b950>

There have been a couple changes in the object model between py2 and py3 which probably explain the different behaviour here, one might be able to find the exact info somewhere in the release notes.

Upvotes: 1

gstukelj
gstukelj

Reputation: 2551

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.

(My emphasis.)

You might also want to have a look at this SO answer.

Upvotes: 1

Related Questions