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

242

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

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

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()
Франк Мезирар
источник
18
Никакая разница, кроме определения method_two (), недопустима, и ее вызов завершается ошибкой.
анатолий техтоник
14
@techtonik: нет ничего плохого в определении method_two! Он вызывается в неверной / недействительной спецификации, то есть с дополнительным аргументом.
0xc0de
1
Ваши методы экземпляра , а не методы класса. Вы создаете метод класса , применяя @classmethodк определению. Первый параметр должен вызываться clsвместо selfи будет получать объект класса, а не экземпляр вашего класса: Test.method_three()и a_test.method_three()эквивалентны.
Лутц Пречелт
Почему вы хотите создать определение функции без selfаргумента? Есть ли сильный вариант использования для этого?
alpha_989

Ответы:

412

В 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(класс класса, см. Этот вопрос ) не создавать связанные методы для 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
Торстен Марек
источник
17
Я поддерживаю этот ответ, он превосходит мой. Молодец Торстен :)
свободное пространство
24
в Python 3 несвязанные методы устарели. вместо этого есть только функция.
Болдник
@boldnik, почему вы говорите, что несвязанные методы устарели? статические методы все еще присутствуют в документации: docs.python.org/3/library/functions.html#staticmethod
alpha_989
195

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

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

Армин Ронахер
источник
12
staticmethodДекоратор оборачивает ваш класс (...) Эта фраза немного вводит в заблуждение , как класс , который обертывают класс от метода , fooа не класс , в котором fooопределен.
Петр Доброгост
12

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

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

method_two = staticmethod(method_two)

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

Джастин Поли
источник
4
Вы имеете в виду «синтаксис декоратора функции @staticmethod».
tzot
11
>>> 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
КЖ
источник
2
Class.instance_method() # The class cannot use an instance methodэто можно использовать. Просто передайте экземпляр вручную:Class.instance_method(a)
warvariuc
@warwaruk Это там, посмотрите на строку ниже TyeErrorлинии.
kzh
да, я видел это позже. тем не менее, имхо, неправильно говорить «класс не может использовать метод экземпляра», потому что вы только что сделали это на одну строку ниже.
warvariuc
@kzh, спасибо за ваше объяснение. Когда вы вызываете a.class_method(), кажется, a.cчто обновился до 1, поэтому вызов Class.class_method(), обновил Class.cпеременную до 2. Однако, когда вы назначили a.c=5, почему не Class.cобновились до 5?
alpha_989
@ alpha_989 python сначала ищет атрибут непосредственно в собственном экземпляре ovjects, а если его там нет, он ищет его в своем классе по умолчанию. Если у вас есть другие вопросы по этому поводу, пожалуйста, не стесняйтесь открыть вопрос и связать его здесь, и я был бы рад помочь в дальнейшем.
kzh
4

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)

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

Джон Кейдж
источник
3

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

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

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' выше) и должен быть предоставлен нами. Однако это потому, что мы называем это в СРО. когда мы вызываем его в экземпляре, нам не нужно указывать его, поскольку среда выполнения может предоставить экземпляр или его класс (полиморфизм)
  • 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 in 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>

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

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

SUPI
источник
1

это ошибка.

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

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)
hayalci
источник
1

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

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

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

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

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

Test.method_two()
MvdD
источник
0

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

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

Yossarian42
источник
0

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

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

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

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

Как описано здесь , 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)который вызовет a, TypeErrorпоскольку метод экземпляра принимает один аргумент, а вы непреднамеренно пытались передать два.

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

Алекса
источник
0

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

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

Питон Новичок
источник
Пожалуйста, добавьте дополнительное объяснение к вашему ответу, чтобы другие могли извлечь из него урок. Например, посмотрите на другие ответы на этот вопрос
Нико Хаасе