Различия в методах класса в Python: связанные, несвязанные и статические - PullRequest
230 голосов
/ 22 сентября 2008

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

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

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

Ответы [ 11 ]

401 голосов
/ 22 сентября 2008

В 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
187 голосов
/ 22 сентября 2008

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

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

11 голосов
/ 23 апреля 2010
>>> 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
11 голосов
/ 22 сентября 2008

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

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

method_two = staticmethod(method_two)

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

4 голосов
/ 22 сентября 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)

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

3 голосов
/ 19 сентября 2016

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

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

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 вызывает метод CPO __get__() для доступа к содержащемуся в нем значению.

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

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

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

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

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

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

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

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

1 голос
/ 19 января 2014

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

1 голос
/ 22 сентября 2008

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

1 голос
/ 22 сентября 2008

это ошибка.

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

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)
1 голос
/ 22 сентября 2008

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

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

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

Test.method_two()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...