Различия в методах классов в 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()

person Franck Mesirard    schedule 22.09.2008    source источник
comment
Никакой разницы, кроме определения method_two (), является недопустимым, и его вызов не выполняется.   -  person anatoly techtonik    schedule 22.07.2012
comment
@techtonik: Нет ничего плохого в определении method_two! Он вызывается в неправильной / недопустимой спецификации, то есть с дополнительным аргументом.   -  person 0xc0de    schedule 10.09.2013
comment
Оба являются методами экземпляра, а не методами класса. Вы создаете метод класса, применяя @classmethod к определение. Первый параметр должен называться cls вместо self, и он получит объект класса, а не экземпляр вашего класса: Test.method_three() и a_test.method_three() эквивалентны.   -  person Lutz Prechelt    schedule 04.08.2016
comment
Зачем вам нужно создавать определение функции без аргумента self? Есть ли у этого веский пример использования?   -  person alpha_989    schedule 11.01.2018


Ответы (13)


В 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 (класс класса, см. this вопрос), чтобы не создавать связанные методы для 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
person Torsten Marek    schedule 22.09.2008
comment
Я поддерживаю этот ответ, он лучше моего. Молодец Торстен :) - person freespace; 22.09.2008
comment
в Python 3 несвязанные методы устарели. вместо этого есть просто функция. - person boldnik; 31.05.2013
comment
@boldnik, почему вы говорите, что несвязанные методы устарели? статические методы все еще присутствуют в документации: docs.python.org/3/library /functions.html#staticmethod - person alpha_989; 11.01.2018
comment
@ alpha_989: (старый комментарий, но все равно) подробности см. в stackoverflow.com/a/11950080. - person djvg; 21.04.2021
comment

Методы в 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>

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

person Armin Ronacher    schedule 22.09.2008
comment
Декоратор staticmethod обертывает ваш класс (...) Эта фраза немного вводит в заблуждение, поскольку оборачиваемый класс является классом метода foo, а не классом в который foo определен. - person Piotr Dobrogost; 25.06.2013
comment
В Python 3 C.foo is C.__dict__['foo']. Из примечания к выпуску Python 3.0: понятие «несвязанные методы» было удалено из языка. При ссылке на метод как на атрибут класса вы теперь получаете простой объект функции. - person djvg; 21.04.2021

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

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

method_two = staticmethod(method_two)

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

person Justin Poliey    schedule 22.09.2008
comment
Вы имеете в виду синтаксис декоратора функции @staticmethod. - person tzot; 22.09.2008

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)

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

person Jon Cage    schedule 22.09.2008

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

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

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 (это может быть экземпляр list, str, function и т. Д.) И предоставляется средой выполнения
  • 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__'

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

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

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

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

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

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

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

Вариант использования статического метода - это, скорее, пространство имен и ремонтопригодность кода (выведение его из класса и доступность во всем модуле и т. Д.).

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

person supi    schedule 18.09.2016

это ошибка.

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

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)
person hayalci    schedule 22.09.2008

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

person Vasil    schedule 22.09.2008
comment
Если я определяю функцию Class Test(object): @staticmethod def method_two(): print(“called method_two”) Я думал об одном варианте использования, когда вы хотите, чтобы функция была частью класса, но не хотите, чтобы пользователь напрямую обращался к функции. Таким образом, method_two может вызываться другими функциями внутри экземпляра Test, но не может быть вызван с помощью a_test.method_two(). Если я использую def method_two(), подойдет ли это для этого варианта использования? Или есть лучший способ изменить определение функции, чтобы оно работало так, как предназначено для вышеуказанного варианта использования? - person alpha_989; 11.01.2018

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

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

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

Test.method_two()
person MvdD    schedule 22.09.2008

Пожалуйста, прочтите эти документы от Гвидо. Первый класс все Ясно объяснил, как рождаются методы Unbound, Bound.

person James    schedule 19.01.2014

Связанный метод = метод экземпляра

Несвязанный метод = статический метод.

person Python Newbie    schedule 01.05.2020
comment
Пожалуйста, добавьте дополнительные пояснения к своему ответу, чтобы другие могли извлечь из него уроки. Например, посмотрите другие ответы на этот вопрос. - person Nico Haase; 01.06.2020

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

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

person Yossarian42    schedule 18.01.2019

Несвязанные методы

Несвязанные методы - это методы, которые еще не привязаны к какому-либо конкретному экземпляру класса.

Связанные методы

Связанные методы - это те, которые привязаны к определенному экземпляру класса.

Как указано в его здесь, self может относиться к разным вещам. в зависимости от функции является связанной, несвязанной или статической.

Взгляните на следующий пример:

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

Поскольку мы не использовали экземпляр класса - obj - в последнем вызове, мы можем сказать, что он выглядит как статический метод.

Если да, то в чем разница между вызовом MyClass.some_method(10) и вызовом статической функции, украшенной @staticmethod декоратором?

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

Кроме того, добавляя декоратор @staticmethod, мы также делаем возможным доступ через объект.

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

Вы не можете выполнить приведенный выше пример с методами экземпляра. Вы можете пережить первый (как мы это делали раньше), но второй будет переведен в несвязанный вызов MyClass.some_method(obj, 10), который вызовет TypeError, поскольку метод экземпляра принимает один аргумент, а вы непреднамеренно пытались передать два.

Затем вы можете сказать: «Если я могу вызывать статические методы как через экземпляр, так и через класс, MyClass.some_static_method и MyClass().some_static_method должны быть одними и теми же методами». Да!

person alexa    schedule 13.02.2020

person    schedule
comment
Class.instance_method() # The class cannot use an instance method его можно использовать. Просто передайте экземпляр вручную: Class.instance_method(a) - person warvariuc; 19.01.2012
comment
@warwaruk Это там, посмотрите на строку под строкой TyeError. - person kzh; 20.01.2012
comment
да, я видел это позже. тем не менее, imo, неправильно говорить «Класс не может использовать метод экземпляра», потому что вы только что сделали это одной строкой ниже. - person warvariuc; 20.01.2012
comment
@kzh, спасибо за объяснение. Когда вы вызывали a.class_method(), кажется, что a.c был обновлен до 1, поэтому вызов Class.class_method() обновил переменную Class.c до 2. Однако, когда вы назначили a.c=5, почему Class.c не обновился до 5? - person alpha_989; 11.01.2018
comment
@ alpha_989 python сначала ищет атрибут непосредственно в собственном экземпляре ovjects, а если его там нет, он по умолчанию ищет его в своем классе. Если у вас есть другие вопросы по этому поводу, не стесняйтесь открывать вопрос и ссылаться на него здесь, и я буду рад помочь в дальнейшем. - person kzh; 11.01.2018