Различия в методах класса в Python: связанные, несвязанные и статические

В чем разница между следующими методами класса?

Это то, что оно статично, а другое - нет?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()

Ответ 1

В Python существует различие между связанными и несвязанными методами.

В принципе, вызов функции-члена (например, method_one), связанная функция

a_test.method_one()

переводится на

Test.method_one(a_test)

то есть. вызов несвязанного метода. Из-за этого вызов вашей версии method_two завершится с ошибкой TypeError

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given) 

Вы можете изменить поведение метода с помощью декоратора

class Test(object):
    def method_one(self):
        print "Called method_one"

    @staticmethod
    def method_two():
        print "Called method two"

Декоратор сообщает встроенный метакласс по умолчанию type (класс класса, cf. этот вопрос), чтобы не создавать связанные методы для method_two.

Теперь вы можете вызвать статический метод как в экземпляре, так и в классе напрямую:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two

Ответ 2

Методы в Python - очень простая вещь, когда вы поняли основы дескрипторной системы. Представьте себе следующий класс:

class C(object):
    def foo(self):
        pass

Теперь давайте посмотрим на этот класс в оболочке:

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

Как вы можете видеть, если вы обращаетесь к атрибуту foo в классе, вы возвращаете несвязанный метод, однако внутри хранилища классов (dict) есть функция. Почему так? Причиной этого является то, что класс вашего класса реализует __getattribute__, который разрешает дескрипторы. Звучит сложно, но нет. C.foo примерно эквивалентен этому коду в этом специальном случае:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

Это потому, что функции имеют метод __get__, который делает их дескрипторами. Если у вас есть экземпляр класса, это почти то же самое, только None является экземпляром класса:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Теперь почему Python это делает? Поскольку объект метода связывает первый параметр функции с экземпляром класса. Это то, откуда приходит я. Теперь иногда вы не хотите, чтобы ваш класс создавал функцию, метод, в котором staticmethod входит в игру:

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

Декоратор staticmethod обертывает ваш класс и реализует фиктивный __get__, который возвращает завернутую функцию как функцию, а не как метод:

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

Надеюсь, что это объяснит.

Ответ 3

Когда вы вызываете члена класса, Python автоматически использует ссылку на объект в качестве первого параметра. Переменная self фактически ничего не значит, это просто соглашение о кодировании. Вы могли бы назвать его gargaloo, если хотите. Тем не менее, вызов method_two приведет к повышению TypeError, поскольку Python автоматически пытается передать параметр (ссылку на его родительский объект) методу, который был определен как не имеющий параметров.

Чтобы заставить его работать, вы можете добавить это к определению вашего класса:

method_two = staticmethod(method_two)

или вы можете использовать @staticmethod декоратор функций.

Ответ 4

>>> 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

Ответ 5

method_two не будет работать, потому что вы определяете функцию-член, но не сообщаете ему, в чем состоит функция. Если вы выполните последнюю строку, вы получите:

>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Если вы определяете функции-члены для класса, первый аргумент всегда должен быть "я".

Ответ 6

Точное объяснение от Армина Ронахера выше, расширяя его ответы, чтобы начинающие, как я, хорошо это понимали:

Разница в методах, определенных в классе, будь то статический или экземплярный метод (есть еще один метод класса классов - здесь не обсуждается, поэтому пропуская его), заключается в том, связаны ли они каким-то образом с экземпляром класса или нет, Например, скажите, получает ли метод ссылку на экземпляр класса во время выполнения

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

Свойство словаря __dict__ объекта класса содержит ссылку на все свойства и методы объекта класса и, следовательно,

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

метод foo доступен, как указано выше. Важно отметить, что все в python является объектом, поэтому ссылки в словаре выше сами указывают на другие объекты. Позвольте мне называть их объектами класса объектов - или как CPO в рамках моего ответа для краткости.

Если CPO является дескриптором, то интерпретатор python вызывает метод __get__() CPO для доступа к содержащемуся ему значению.

Чтобы определить, является ли CPO дескриптором, интерпретатор python проверяет, реализует ли он протокол дескриптора. Для реализации протокола дескриптора необходимо реализовать 3 метода

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

например.

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

где

  • self - это CPO (это может быть экземпляр списка, str, функция и т.д.) и предоставляется во время выполнения
  • instance - это экземпляр класса, в котором этот CPO определен (объект 'c' выше) и должен быть предоставлен нами явно.
  • owner - это класс, в котором этот CPO определен (объект класса "C" выше) и должен быть предоставлен нами. Однако это потому, что мы вызываем его на CPO. когда мы вызываем его в экземпляре, нам не нужно его предоставлять, поскольку среда выполнения может предоставить экземпляр или его класс (полиморфизм).
  • value является предполагаемым значением для CPO и должно быть предоставлено нами

Не все CPO являются дескрипторами. Например

>>> 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__'

Это связано с тем, что класс list не реализует протокол дескриптора.

Таким образом, аргумент self в c.foo(self) требуется, потому что его сигнатура метода на самом деле это C.__dict__['foo'].__get__(c, C) (как объяснялось выше, C не требуется, поскольку его можно обнаружить или полиморфно) И именно поэтому вы получаете TypeError, если вы не передаете требуемый аргумент экземпляра.

Если вы заметили, что метод по-прежнему ссылается через класс Object C, а привязка к экземпляру класса достигается путем передачи в эту функцию контекста в форме объекта экземпляра.

Это довольно удивительно, поскольку, если вы решили не сохранять контекст или не связываться с экземпляром, все, что было необходимо, это написать класс, чтобы обернуть дескриптор CPO и переопределить его метод __get__(), чтобы не требовать контекста. Этот новый класс - это то, что мы называем декоратором и применяем через ключевое слово @staticmethod

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

Отсутствие контекста в новом обернутом CPO foo не вызывает ошибку и может быть проверено следующим образом:

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

Случай использования статического метода - это скорее поддержка пространства имен и обслуживания кода (выведение его из класса и его доступность по всему модулю и т.д.).

Возможно, лучше писать статические методы, а не методы экземпляра, когда это возможно, если только вам не нужно консексуализировать методы (например, переменные экземпляра доступа, переменные класса и т.д.). Одна из причин - облегчить сбор мусора, не сохраняя нежелательной ссылки на объекты.

Ответ 7

Вызов метода method_two будет генерировать исключение, не принимающее параметр self, время выполнения Python автоматически пройдет его.

Если вы хотите создать статический метод в классе Python, украсьте его с помощью staticmethod decorator.

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()

Ответ 8

это ошибка.

прежде всего, первая строка должна быть такой (будьте осторожны с капиталами)

class Test(object):

Всякий раз, когда вы вызываете метод класса, он получает в качестве первого аргумента (отсюда и имя self) и method_two дает эту ошибку

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Ответ 9

Второй не будет работать, потому что, когда вы вызываете его, как будто этот python внутренне пытается вызвать его с экземпляром a_test в качестве первого аргумента, но ваш метод method_two не принимает никаких аргументов, поэтому он не будет работать, вы будете получить ошибку времени выполнения. Если вы хотите эквивалент статического метода, вы можете использовать метод класса. Намного меньше нужно для методов класса в Python, чем статические методы на таких языках, как Java или С#. Чаще всего лучшим решением является использование метода в модуле, вне определения класса, которые работают более эффективно, чем методы класса.

Ответ 10

Пожалуйста, прочитайте эти документы из Guido First Class all Четко объяснили, как рождаются методы Unbound, Bound.

Ответ 11

Определение method_two. Когда вы вызываете method_two, вы получите TypeError: method_two() takes 0 positional arguments but 1 was given от интерпретатора.

Метод экземпляра является ограниченной функцией, когда вы вызываете ее как a_test.method_two(). Он автоматически принимает self, что указывает на экземпляр Test, в качестве первого параметра. С помощью параметра self метод экземпляра может свободно обращаться к атрибутам и изменять их для одного и того же объекта.