Różnice metod klas w Pythonie: powiązane, niezwiązane i statyczne

242

Jaka jest różnica między następującymi metodami klasowymi?

Czy to jeden jest statyczny, a drugi nie?

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()
Franck Mesirard
źródło
18
Żadna różnica poza definicją metody method_two () jest niepoprawna i jej wywołanie kończy się niepowodzeniem.
anatoly techtonik
14
@techtonik: W definicji metody method_two nie ma nic złego! Jest wywoływany w niepoprawnej / niepoprawnej specyfikacji, tj. Z dodatkowym argumentem.
0xc0de,
1
Twoje są metodami instancji , a nie metodami klas. Metodę klasy tworzy się , stosując @classmethodsię do definicji. Pierwszy parametr powinien zostać wywołany clszamiast selfi otrzyma obiekt klasy, a nie instancję klasy: Test.method_three()i a_test.method_three()są równoważne.
Lutz Prechelt
Dlaczego chcesz utworzyć definicję funkcji bez selfargumentu? Czy jest na to mocny przypadek użycia?
alpha_989

Odpowiedzi:

412

W Pythonie istnieje rozróżnienie między metodami związanymi i niezwiązanymi .

Zasadniczo, wywołanie funkcji członka (jak method_one), funkcji powiązanej

a_test.method_one()

jest przetłumaczone na

Test.method_one(a_test)

tj. wywołanie metody niezwiązanej. Z tego powodu wywołanie do Twojej wersji method_twonie powiedzie się zTypeError

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

Możesz zmienić zachowanie metody za pomocą dekoratora

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

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

Dekorator informuje wbudowaną domyślną metaklasę type(klasę klasy, por. To pytanie ), aby nie tworzyła powiązanych metod method_two.

Teraz możesz wywoływać metodę statyczną bezpośrednio w instancji lub w klasie bezpośrednio:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
Torsten Marek
źródło
17
Popieram tę odpowiedź, jest lepsza od mojej. Dobra robota Torsten :)
Freespace
24
w Pythonie 3 niezwiązane metody są przestarzałe. zamiast tego jest tylko funkcja.
boldnik
@boldnik, dlaczego uważasz, że niezwiązane metody są przestarzałe? metody statyczne są nadal obecne w dokumentacji: docs.python.org/3/library/functions.html#staticmethod
alpha_989
195

Metody w Pythonie są bardzo, bardzo proste, gdy zrozumiesz podstawy systemu deskryptorów. Wyobraź sobie następującą klasę:

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

Teraz spójrzmy na tę klasę w powłoce:

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

Jak widać, jeśli uzyskasz dostęp do fooatrybutu klasy, otrzymasz niezwiązaną metodę, jednak wewnątrz pamięci klas (dykt) znajduje się funkcja. Dlaczego tak jest Powodem tego jest to, że klasa twojej klasy implementuje __getattribute__deskryptory. Brzmi skomplikowanie, ale nie jest. C.foojest mniej więcej równoważne z tym kodem w tym szczególnym przypadku:

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

To dlatego, że funkcje mają __get__metodę, która czyni je deskryptorami. Jeśli masz instancję klasy, jest ona prawie taka sama, tylko taka Nonejest instancja klasy:

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

Dlaczego Python to robi? Ponieważ obiekt metody wiąże pierwszy parametr funkcji z instancją klasy. Stąd pochodzi jaźń. Czasami nie chcesz, aby twoja klasa uczyniła funkcję metodą, oto co staticmethodwchodzi w grę:

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

staticmethodDekorator zawija swoją klasę i wdraża atrapę __get__, która zwraca funkcję zawinięte w funkcji a nie jako metoda:

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

Mam nadzieję, że to wyjaśnia.

Armin Ronacher
źródło
12
staticmethodDekorator owija klasa (...) To zdanie jest nieco mylące, jako klasa, która jest owinięta jest klasa z metodą foo, a nie klasa, w której foojest zdefiniowana.
Piotr Dobrogost
12

Kiedy wywołujesz członka klasy, Python automatycznie używa odwołania do obiektu jako pierwszego parametru. Zmienna selftak naprawdę nic nie znaczy, to tylko konwencja kodowania. Możesz to nazwać, gargaloojeśli chcesz. To powiedziawszy, wywołanie method_twowywołałoby a TypeError, ponieważ Python automatycznie próbuje przekazać parametr (odwołanie do swojego obiektu nadrzędnego) do metody, która została zdefiniowana jako nie posiadająca parametrów.

Aby faktycznie działało, możesz dołączyć to do definicji klasy:

method_two = staticmethod(method_two)

lub możesz użyć @staticmethod dekoratora funkcji .

Justin Poliey
źródło
4
Masz na myśli „składnię dekoratora funkcji @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
kzh
źródło
2
Class.instance_method() # The class cannot use an instance methodmoże użyć. Wystarczy przekazać instancję ręcznie:Class.instance_method(a)
warvariuc
@warwaruk Jest tam, spójrz na linię poniżej TyeErrorlinii.
kzh
tak, widziałem to później. wciąż, imo, nie jest poprawne powiedzenie „Klasa nie może użyć metody instancji”, ponieważ właśnie zrobiłeś to jeden wiersz poniżej.
warvariuc
@ kzh, dziękuję za wyjaśnienie. Kiedy zadzwoniłeś a.class_method(), wygląda na to, że a.czostał zaktualizowany 1, więc wywołanie Class.class_method(), zaktualizowało Class.czmienną do 2. Jednak po przypisaniu a.c=5dlaczego Class.cnie zaktualizowano się do 5?
alpha_989
@ alpha_989 python najpierw szuka atrybutu bezpośrednio w własnej instancji, a jeśli go nie ma, domyślnie szuka go w swojej klasie. Jeśli masz inne pytania na ten temat, możesz otworzyć pytanie i połączyć je tutaj, a chętnie pomogę.
kzh
4

metoda_two nie będzie działać, ponieważ definiujesz funkcję składową, ale nie mówisz jej, do której funkcji należy. Jeśli wykonasz ostatni wiersz, otrzymasz:

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

Jeśli definiujesz funkcje składowe dla klasy, pierwszym argumentem zawsze musi być „ja”.

Jon Cage
źródło
3

Dokładne wyjaśnienie od Armin Ronacher powyżej, rozszerzając jego odpowiedzi, aby początkujący tacy jak ja dobrze to rozumieli:

Różnica w metodach zdefiniowanych w klasie, zarówno statycznych, jak i instancji (istnieje jeszcze inna metoda typu - klasa - nie omawiana tutaj, więc pomijanie jej) polega na tym, czy są one w jakiś sposób powiązane z instancją klasy, czy nie. Na przykład powiedz, czy metoda otrzymuje odwołanie do instancji klasy podczas działania

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

Właściwość __dict__Dictionary obiektu klasy zawiera odniesienie do wszystkich właściwości i metod obiektu klasy, a tym samym

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

metoda foo jest dostępna jak wyżej. Ważną kwestią, na którą należy tutaj zwrócić uwagę, jest to, że wszystko w Pythonie jest obiektem, a zatem odwołania w powyższym słowniku wskazują same obiekty. Nazwijmy je Obiektami własności klasy - lub CPO w zakresie mojej odpowiedzi za zwięzłość.

Jeśli deskryptor CPO jest deskryptorem, wówczas interpreter Pythona wywołuje __get__()metodę CPO w celu uzyskania dostępu do wartości, którą zawiera.

Aby ustalić, czy CPO jest deskryptorem, interpreter Pythona sprawdza, czy implementuje protokół deskryptora. Aby wdrożyć protokół deskryptora, należy zaimplementować 3 metody

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

na przykład

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

gdzie

  • self jest CPO (może to być instancja listy, str, funkcji itp.) i jest dostarczana przez środowisko wykonawcze
  • instance jest instancją klasy, w której ten CPO jest zdefiniowany (obiekt „c” powyżej) i musi być dostarczony przez nas jawność
  • ownerto klasa, w której zdefiniowano ten CPO (obiekt klasy „C” powyżej) i należy go dostarczyć. Jest tak jednak dlatego, że nazywamy to CPO. gdy wywołujemy go w instancji, nie musimy go podawać, ponieważ środowisko wykonawcze może dostarczyć instancję lub jej klasę (polimorfizm)
  • value jest zamierzoną wartością CPO i musi być przez nas dostarczona

Nie wszystkie CPO są deskryptorami. Na przykład

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

Jest tak, ponieważ klasa listy nie implementuje protokołu deskryptora.

Zatem argument self in c.foo(self)jest wymagany, ponieważ jego podpis metody jest w rzeczywistości taki C.__dict__['foo'].__get__(c, C)(jak wyjaśniono powyżej, C nie jest potrzebny, ponieważ można go znaleźć lub polimorficznie). I dlatego otrzymujesz błąd typu TypeError, jeśli nie przekażesz wymaganego argumentu instancji.

Jeśli zauważysz, że do metody nadal odwołuje się obiekt klasy C, a powiązanie z instancją klasy jest osiągane poprzez przekazanie kontekstu w postaci obiektu instancji do tej funkcji.

Jest to całkiem niesamowite, ponieważ jeśli zdecydujesz się nie utrzymywać kontekstu ani powiązania z instancją, wystarczyło napisać klasę, aby owinąć CPO deskryptora i zastąpić jej __get__()metodę, aby nie wymagała kontekstu. Ta nowa klasa nazywana jest dekoratorem i jest stosowana za pomocą słowa kluczowego@staticmethod

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

Brak kontekstu w nowym opakowanym CPO foonie generuje błędu i można to zweryfikować w następujący sposób:

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

Przypadek użycia metody statycznej jest bardziej przestrzenią nazw i utrzymywalnością kodu (usunięcie go z klasy i udostępnienie w całym module itp.).

Może lepiej jest pisać metody statyczne niż metody instancji, o ile to możliwe, chyba że oczywiście trzeba kontekstualizować metody (takie jak zmienne instancji dostępu, zmienne klas itp.). Jednym z powodów jest ułatwienie wyrzucania elementów bezużytecznych przez niepotrzebne odwoływanie się do obiektów.

supi
źródło
1

to jest błąd.

po pierwsze, pierwsza linia powinna być taka (uważaj na wielkie litery)

class Test(object):

Ilekroć wywołujesz metodę klasy, staje się ona pierwszym argumentem (stąd nazwa self), a metoda_two podaje ten błąd

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)
Hayalci
źródło
1

Drugi nie zadziała, ponieważ gdy wywołasz go w ten sposób, python wewnętrznie próbuje wywołać go z instancją a_test jako pierwszym argumentem, ale twoja metoda_two nie przyjmuje żadnych argumentów, więc nie będzie działać, otrzymasz środowisko wykonawcze błąd. Jeśli chcesz ekwiwalent metody statycznej, możesz użyć metody klasy. Metody klas w Pythonie są znacznie mniejsze niż metody statyczne w językach takich jak Java lub C #. Najczęściej najlepszym rozwiązaniem jest użycie metody w module, poza definicją klasy, która działa wydajniej niż metoda klasy.

Wasil
źródło
Jeśli zdefiniuję funkcję. Class Test(object): @staticmethod def method_two(): print(“called method_two”) Jednym z przypadków użycia, o którym myślałem, było to, że chcesz, aby funkcja była częścią klasy, ale nie chcesz, aby użytkownik miał bezpośredni dostęp do funkcji. Więc method_twomogą być wywoływane przez inne funkcje wewnątrz Testinstancji, ale nie mogą być wywoływane za pomocą a_test.method_two(). Jeśli użyję def method_two(), czy to zadziała w tym przypadku użycia? Czy jest lepszy sposób modyfikacji definicji funkcji, więc działa ona zgodnie z przeznaczeniem dla powyższego przypadku użycia?
alpha_989
1

Wywołanie metody method_two spowoduje zgłoszenie wyjątku z powodu nieakceptowania parametru własnego, który środowisko wykonawcze Python automatycznie przekaże.

Jeśli chcesz utworzyć metodę statyczną w klasie Python, udekoruj ją za pomocą staticmethod decorator.

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

Test.method_two()
MvdD
źródło
1

Proszę przeczytać ten dokument z Pierwszej Klasy Guido wszystko Wyraźnie wyjaśniło, w jaki sposób powstają metody Unbound, Bound.

James Sapam
źródło
0

Definicja method_twojest nieprawidłowa. Kiedy zadzwonisz method_two, dostaniesz TypeError: method_two() takes 0 positional arguments but 1 was givenod tłumacza.

Metoda instancji jest funkcją ograniczoną, gdy ją wywołujesz a_test.method_two(). Automatycznie przyjmuje self, co wskazuje na wystąpienie Test, jako swój pierwszy parametr. Za pomocą selfparametru metoda instancji może swobodnie uzyskiwać dostęp do atrybutów i modyfikować je w tym samym obiekcie.

Yossarian42
źródło
0

Metody niezwiązane

Metody niezwiązane to metody, które nie są jeszcze powiązane z żadną konkretną instancją klasy.

Metody powiązane

Metody powiązane to te, które są powiązane z konkretnym wystąpieniem klasy.

Jak tu udokumentowano , self może odnosić się do różnych rzeczy w zależności od funkcji jest związana, niezwiązana lub statyczna.

Spójrz na następujący przykład:

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

Ponieważ nie użyliśmy instancji klasy obj- przy ostatnim wywołaniu możemy powiedzieć, że wygląda jak metoda statyczna.

Jeśli tak, to jaka jest różnica między MyClass.some_method(10)wywołaniem a wywołaniem funkcji statycznej ozdobionej @staticmethoddekoratorem?

Korzystając z dekoratora, wyraźnie zaznaczamy, że metoda zostanie użyta bez uprzedniego utworzenia dla niej instancji. Zwykle nie można oczekiwać, że metody członków klasy będą używane bez instancji, a dostęp do nich może powodować ewentualne błędy w zależności od struktury metody.

Dodając @staticmethoddekorator, umożliwiamy również dostęp do obiektu.

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

Nie możesz zrobić powyższego przykładu metodami instancji. Możesz przetrwać pierwszy (tak jak poprzednio), ale drugi zostanie przetłumaczony na niezwiązane wywołanie, MyClass.some_method(obj, 10)które podniesie, TypeErrorponieważ metoda instancji przyjmuje jeden argument i nieumyślnie próbujesz przekazać dwa.

Następnie możesz powiedzieć: „jeśli mogę wywoływać metody statyczne zarówno przez instancję, jak i klasę, MyClass.some_static_methodi MyClass().some_static_methodpowinny to być te same metody”. Tak!

Alexa
źródło
0

Metoda powiązana = metoda instancji

Metoda niezwiązana = metoda statyczna.

Python Newbie
źródło
Dodaj dodatkowe wyjaśnienie do swojej odpowiedzi, aby inni mogli się z niej uczyć. Na przykład spójrz na inne odpowiedzi na to pytanie
Nico Haase