Class method differences in Python: bound, unbound and static

后端 未结 13 1109
日久生厌
日久生厌 2020-11-22 08:54

What is the difference between the following class methods?

Is it that one is static and the other is not?

class Test(object):
  def method_one(self)         


        
相关标签:
13条回答
  • 2020-11-22 09:35

    Unbound Methods

    Unbound methods are methods that are not bound to any particular class instance yet.

    Bound Methods

    Bound methods are the ones which are bound to a specific instance of a class.

    As its documented here, self can refer to different things depending on the function is bound, unbound or static.

    Take a look at the following example:

    class MyClass:    
        def some_method(self):
            return self  # For the sake of the example
    
    >>> MyClass().some_method()
    <__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()
    
    >>> obj.some_method()
    <__main__.MyClass object at 0x10ea12bb0>
    
    # Bound method call:
    >>> obj.some_method(10)
    TypeError: some_method() takes 1 positional argument but 2 were given
    
    # WHY IT DIDN'T WORK?
    # obj.some_method(10) bound call translated as
    # MyClass.some_method(obj, 10) unbound method and it takes 2 
    # arguments now instead of 1 
    
    # ----- USING THE UNBOUND METHOD ------
    >>> MyClass.some_method(10)
    10
    

    Since we did not use the class instance — obj — on the last call, we can kinda say it looks like a static method.

    If so, what is the difference between MyClass.some_method(10) call and a call to a static function decorated with a @staticmethod decorator?

    By using the decorator, we explicitly make it clear that the method will be used without creating an instance for it first. Normally one would not expect the class member methods to be used without the instance and accesing them can cause possible errors depending on the structure of the method.

    Also, by adding the @staticmethod decorator, we are making it possible to be reached through an object as well.

    class MyClass:    
        def some_method(self):
            return self    
    
        @staticmethod
        def some_static_method(number):
            return number
    
    >>> MyClass.some_static_method(10)   # without an instance
    10
    >>> MyClass().some_static_method(10)   # Calling through an instance
    10
    

    You can’t do the above example with the instance methods. You may survive the first one (as we did before) but the second one will be translated into an unbound call MyClass.some_method(obj, 10) which will raise a TypeError since the instance method takes one argument and you unintentionally tried to pass two.

    Then, you might say, “if I can call static methods through both an instance and a class, MyClass.some_static_method and MyClass().some_static_method should be the same methods.” Yes!

    0 讨论(0)
  • 2020-11-22 09:37

    The call to method_two will throw an exception for not accepting the self parameter the Python runtime will automatically pass it.

    If you want to create a static method in a Python class, decorate it with the staticmethod decorator.

    Class Test(Object):
      @staticmethod
      def method_two():
        print "Called method_two"
    
    Test.method_two()
    
    0 讨论(0)
  • 2020-11-22 09:40

    The second one won't work because when you call it like that python internally tries to call it with the a_test instance as the first argument, but your method_two doesn't accept any arguments, so it wont work, you'll get a runtime error. If you want the equivalent of a static method you can use a class method. There's much less need for class methods in Python than static methods in languages like Java or C#. Most often the best solution is to use a method in the module, outside a class definition, those work more efficiently than class methods.

    0 讨论(0)
  • 2020-11-22 09:41

    When you call a class member, Python automatically uses a reference to the object as the first parameter. The variable self actually means nothing, it's just a coding convention. You could call it gargaloo if you wanted. That said, the call to method_two would raise a TypeError, because Python is automatically trying to pass a parameter (the reference to its parent object) to a method that was defined as having no parameters.

    To actually make it work, you could append this to your class definition:

    method_two = staticmethod(method_two)
    

    or you could use the @staticmethod function decorator.

    0 讨论(0)
  • 2020-11-22 09:43
    >>> class Class(object):
    ...     def __init__(self):
    ...         self.i = 0
    ...     def instance_method(self):
    ...         self.i += 1
    ...         print self.i
    ...     c = 0
    ...     @classmethod
    ...     def class_method(cls):
    ...         cls.c += 1
    ...         print cls.c
    ...     @staticmethod
    ...     def static_method(s):
    ...         s += 1
    ...         print s
    ... 
    >>> a = Class()
    >>> a.class_method()
    1
    >>> Class.class_method()    # The class shares this value across instances
    2
    >>> a.instance_method()
    1
    >>> Class.instance_method() # The class cannot use an instance method
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
    >>> Class.instance_method(a)
    2
    >>> b = 0
    >>> a.static_method(b)
    1
    >>> a.static_method(a.c) # Static method does not have direct access to 
    >>>                      # class or instance properties.
    3
    >>> Class.c        # a.c above was passed by value and not by reference.
    2
    >>> a.c
    2
    >>> a.c = 5        # The connection between the instance
    >>> Class.c        # and its class is weak as seen here.
    2
    >>> Class.class_method()
    3
    >>> a.c
    5
    
    0 讨论(0)
  • 2020-11-22 09:44

    Accurate explanation from Armin Ronacher above, expanding on his answers so that beginners like me understand it well:

    Difference in the methods defined in a class, whether static or instance method(there is yet another type - class method - not discussed here so skipping it), lay in the fact whether they are somehow bound to the class instance or not. For example, say whether the method receives a reference to the class instance during runtime

    class C:
        a = [] 
        def foo(self):
            pass
    
    C # this is the class object
    C.a # is a list object (class property object)
    C.foo # is a function object (class property object)
    c = C() 
    c # this is the class instance
    

    The __dict__ dictionary property of the class object holds the reference to all the properties and methods of a class object and thus

    >>> C.__dict__['foo']
    <function foo at 0x17d05b0>
    

    the method foo is accessible as above. An important point to note here is that everything in python is an object and so references in the dictionary above are themselves pointing to other objects. Let me call them Class Property Objects - or as CPO within the scope of my answer for brevity.

    If a CPO is a descriptor, then python interpretor calls the __get__() method of the CPO to access the value it contains.

    In order to determine if a CPO is a descriptor, python interpretor checks if it implements the descriptor protocol. To implement descriptor protocol is to implement 3 methods

    def __get__(self, instance, owner)
    def __set__(self, instance, value)
    def __delete__(self, instance)
    

    for e.g.

    >>> C.__dict__['foo'].__get__(c, C)
    

    where

    • self is the CPO (it could be an instance of list, str, function etc) and is supplied by the runtime
    • instance is the instance of the class where this CPO is defined (the object 'c' above) and needs to be explicity supplied by us
    • owner is the class where this CPO is defined(the class object 'C' above) and needs to be supplied by us. However this is because we are calling it on the CPO. when we call it on the instance, we dont need to supply this since the runtime can supply the instance or its class(polymorphism)
    • value is the intended value for the CPO and needs to be supplied by us

    Not all CPO are descriptors. For example

    >>> C.__dict__['foo'].__get__(None, C)
    <function C.foo at 0x10a72f510> 
    >>> C.__dict__['a'].__get__(None, C)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'list' object has no attribute '__get__'
    

    This is because the list class doesnt implement the descriptor protocol.

    Thus the argument self in c.foo(self) is required because its method signature is actually this C.__dict__['foo'].__get__(c, C) (as explained above, C is not needed as it can be found out or polymorphed) And this is also why you get a TypeError if you dont pass that required instance argument.

    If you notice the method is still referenced via the class Object C and the binding with the class instance is achieved via passing a context in the form of the instance object into this function.

    This is pretty awesome since if you chose to keep no context or no binding to the instance, all that was needed was to write a class to wrap the descriptor CPO and override its __get__() method to require no context. This new class is what we call a decorator and is applied via the keyword @staticmethod

    class C(object):
      @staticmethod
      def foo():
       pass
    

    The absence of context in the new wrapped CPO foo doesnt throw an error and can be verified as follows:

    >>> C.__dict__['foo'].__get__(None, C)
    <function foo at 0x17d0c30>
    

    Use case of a static method is more of a namespacing and code maintainability one(taking it out of a class and making it available throughout the module etc).

    It maybe better to write static methods rather than instance methods whenever possible, unless ofcourse you need to contexualise the methods(like access instance variables, class variables etc). One reason is to ease garbage collection by not keeping unwanted reference to objects.

    0 讨论(0)
提交回复
热议问题