Різниці методів класу в 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: Нічого поганого у визначенні методу_дво! Його викликають у неправильній / недійсній специфікації, тобто з додатковим аргументом.
0xc0de

1
Ваші обидва методи екземпляра , а не методи класу. Ви створюєте метод класу , застосовуючи @classmethodдо визначення. Перший параметр слід викликати clsзамість selfі отримає об’єкт class, а не екземпляр вашого класу: Test.method_three()і a_test.method_three()є еквівалентом.
Lutz Prechelt

Чому ви хочете створити визначення функції без 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
Я підтримую цю відповідь, вона перевершує мою. Молодці Torsten :)
простір

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атрибуту класу, ви отримуєте назад незв'язаний метод, проте всередині класового сховища (диктату) є функція. Чому це? Причиною цього є те, що клас вашого класу реалізує форму, __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якщо хочете. Однак, заклик до method_twoзалучення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рядком.
кж

так, я побачив це пізніше. все-таки, imo, неправильно сказати "Клас не може використовувати метод екземпляра", тому що ви просто зробили це в одному рядку нижче.
warvariuc

@kzh, Дякую за ваше пояснення. Коли ви зателефонували a.class_method(), схоже, a.cоновили до 1, тому виклик the Class.class_method(), оновив Class.cзмінну до 2. Однак, призначивши a.c=5, чому ви Class.cне оновлювались 5?
alpha_989

@ alpha_989 python спочатку шукає атрибут безпосередньо у власному екземплярі ovjects, і якщо його немає, він шукає його у своєму класі за замовчуванням. Якщо у вас є якісь інші запитання з цього приводу, будь ласка, не соромтесь відкрити питання та зв’язати його тут, і я б радий допомогти далі.
кж

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)

Якщо ви визначаєте функції члена для класу, першим аргументом завжди має бути "я".


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 є дескриптором, то інтерпретатор python викликає __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 є СРО (це може бути екземпляр списку, str, функції тощо) і постачається під час виконання
  • instance - це екземпляр класу, де цей CPO визначений (об'єкт 'c' вище) і повинен бути наданий нам explicity
  • owner- це клас, де цей CPO визначений (об'єкт класу 'C' вище) і його потрібно поставити нами. Однак це тому, що ми називаємо це на СРО. коли ми називаємо це екземпляром, нам не потрібно подавати це, оскільки час виконання може постачати екземпляр або його клас (поліморфізм)
  • value - це цільове значення для СРО і має бути надане нами

Не всі 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, а зв'язування з екземпляром класу досягається шляхом передачі контексту у формі об'єкта екземпляра в цю функцію.

Це досить приголомшливо, оскільки якщо ви вирішили не зберігати жодний контекст або не прив'язувати його до екземпляра, все, що потрібно, було написати клас, щоб обернути дескрипторну СРО та змінити її __get__()метод, щоб не вимагати контексту. Цей новий клас - це те, що ми називаємо декоратором, і застосовується за допомогою ключового слова@staticmethod

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

Відсутність контексту в новому завершеному CPO fooне призводить до помилки, і його можна перевірити наступним чином:

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

Випадок використання статичного методу є більшою частиною простору імен та ремонту коду (виведення його з класу та надання доступу до модуля тощо).

Можливо, краще писати статичні методи, а не методи екземплярів, коли це можливо, якщо, звичайно, вам не потрібно контекстуалізувати методи (наприклад, змінні екземпляри доступу, змінні класу тощо). Одна з причин - полегшити збирання сміття, не зберігаючи небажані посилання на предмети.


1

це помилка.

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

class Test(object):

Кожного разу, коли ви викликаєте метод класу, він отримує себе як перший аргумент (звідси і назва себе), і 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

Другий не буде працювати, тому що, коли ви називаєте його як той 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()


0

Визначення method_twoнедійсне. Коли зателефонуєте method_two, ви отримаєтеTypeError: method_two() takes 0 positional arguments but 1 was given перекладача.

Метод екземпляра - це обмежена функція, коли ви називаєте це як a_test.method_two(). Він автоматично приймає self, що вказує на екземпляр Test, як свій перший параметр. Через selfпараметр метод екземпляра може вільно отримувати доступ до атрибутів і змінювати їх на одному об'єкті.


0

Несполучені методи

Несполучені методи - це методи, які ще не пов'язані з будь-яким конкретним екземпляром класу.

Сполучені методи

Зв'язані методи - це ті, які пов'язані з певним екземпляром класу.

Як тут задокументовано , я може посилатися на різні речі залежно від функції, зв'язаної, незв'язаної чи статичної.

Погляньте на наступний приклад:

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повинні бути однакові методи". Так!


0

Зв'язаний метод = метод екземпляра

Спосіб без зв’язку = статичний метод.


Будь ласка, додайте до своєї відповіді дещо додаткове пояснення, щоб інші могли дізнатися з неї. Наприклад, погляньте на інші відповіді на це питання
Ніко Хааз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.