Python'da sınıf yöntemi farklılıkları: bağlı, bağlı ve statik


242

Aşağıdaki sınıf yöntemleri arasındaki fark nedir?

Biri statik ve diğeri değil mi?

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 () tanımı dışında bir fark geçersiz ve çağrısı başarısız.
anatoly techtonik

14
@techtonik: method_two'nun tanımı ile yanlış bir şey yok! Yanlış / geçersiz bir spesifikasyonda, yani ekstra bir argümanla çağrılıyor.
0xc0de

1
Sizinki sınıf yöntemi değil , her iki örnek yöntemdir. Tanıma uygulayarak bir sınıf yöntemi oluşturursunuz @classmethod. İlk parametre clsyerine çağrılmalı selfve sınıfınızın bir örneği yerine sınıf nesnesini alacaktır: Test.method_three()ve a_test.method_three()eşdeğerdir.
Lutz Prechelt

Neden selfbağımsız değişken olmadan bir işlev tanımı oluşturmak istersiniz ? Bunun için güçlü bir kullanım durumu var mı?
alpha_989

Yanıtlar:


412

Python'da bağlı ve bağlı olmayan yöntemler arasında bir ayrım vardır .

Temel olarak, üye işlevine (ör. method_one) Çağrı , bağlı işlev

a_test.method_one()

çevrildi

Test.method_one(a_test)

örneğin, bağlanmamış bir yönteme çağrı. Bu nedenle, sürümü için bir çağrı method_twoa başarısız olurTypeError

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

Dekoratör kullanarak bir yöntemin davranışını değiştirebilirsiniz

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

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

Dekoratör, yerleşik varsayılan metasınıfa type(bir sınıfın sınıfı, bu soruya bakın ) bağlı yöntemler oluşturmamasını söyler method_two.

Artık statik örneği hem örnek üzerinde hem de sınıfta doğrudan çağırabilirsiniz:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two

17
Bu cevabı destekliyorum, benimkinden daha üstündür. Aferin Torsten :)
freespace

24
python 3'te bağlanmamış yöntemler kullanımdan kaldırılmıştır. bunun yerine sadece bir işlev vardır.
boldnik

@boldnik, neden ilişkisiz yöntemlerin kullanımdan kaldırıldığını söylüyorsunuz? belgelerde hala statik yöntemler bulunmaktadır: docs.python.org/3/library/functions.html#staticmethod
alpha_989

195

Tanımlayıcı sistemin temellerini anladıktan sonra Python'daki yöntemler çok ama çok basit bir şeydir. Aşağıdaki sınıfı düşünün:

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

Şimdi kabuktaki o sınıfa bir göz atalım:

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

fooSınıftaki özniteliğe erişip erişmediğinizi görebileceğiniz gibi , sınırsız bir yöntem elde edersiniz, ancak sınıf depolama (dict) içinde bir işlev vardır. Nedenmiş? Bunun nedeni, sınıfınızın sınıfının __getattribute__tanımlayıcıları çözen bir sınıf uygulamasıdır . Kulağa karmaşık geliyor, ama değil. C.foobu özel durumda kabaca bu koda eşdeğerdir:

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

Çünkü işlevlerin __get__onları tanımlayıcı yapan bir yöntemi vardır. Bir sınıf örneğiniz varsa hemen hemen aynıdır, bu sadece Nonesınıf örneğidir:

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

Python neden bunu yapıyor? Çünkü method nesnesi bir fonksiyonun ilk parametresini sınıf örneğine bağlar. Benlik buradan gelir. Şimdi bazen sınıfınızın bir işlevi bir yöntem haline getirmesini istemezsiniz, işte burada staticmethoddevreye girer:

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

staticmethodDekoratör sınıfınızın ve uygular bir kukla sarar __get__bir yöntem olarak işlev olarak sarılmış işlevini döndürür değil:

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

Umarım bunu açıklar.


12
staticmethodDekoratör sınıf (...) sarar Bu tabir sarılı olan sınıf olarak yanıltıcı biraz sınıftır olduğu bir yöntemle foodeğil hangi sınıf footanımlanır.
Piotr Dobrogost

12

Bir sınıf üyesini çağırdığınızda, Python otomatik olarak ilk parametre olarak nesneye bir başvuru kullanır. Değişken selfaslında hiçbir şey ifade etmiyor, sadece bir kodlama kuralı. gargalooEğer istersen arayabilirsin . Bununla birlikte, çağrı , Python'un parametre içermediği tanımlanan bir yönteme otomatik olarak bir parametre (üst nesnesine başvuru) iletmeye çalıştığı için method_twobir çağrı yapacaktır TypeError.

Aslında çalışmasını sağlamak için bunu sınıf tanımınıza ekleyebilirsiniz:

method_two = staticmethod(method_two)

veya @staticmethod fonksiyon dekoratörünü kullanabilirsiniz .


4
"@Staticmethod fonksiyon dekoratörü sözdizimi" demek istediniz.
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 methodkullanabilir. Sadece örneği manuel olarak Class.instance_method(a)
iletin

@warwaruk Orada, çizginin altındaki çizgiye bakın TyeError.
kzh

evet sonra gördüm. yine de, imo, 'Sınıf bir örnek yöntemi kullanamaz' demek doğru değil, çünkü bunu sadece bir satır aşağıda yaptınız.
warvariuc

@kzh, Açıklaman için teşekkürler. Aradığınızda a.class_method(), a.cgüncellenmiş gibi görünüyor 1, bu nedenle çağrı Class.class_method(), Class.cdeğişkeni güncelledi 2. Ancak, atadığınızda a.c=5, neden Class.cgüncellenmedi 5?
alpha_989

@ alpha_989 python önce doğrudan bir nesnenin kendi örneğinde bir öznitelik arar ve orada yoksa, varsayılan olarak sınıfında arar. Bununla ilgili başka sorularınız varsa, lütfen bir soru açıp buraya bağlantı kurmaktan çekinmeyin, size daha fazla yardımcı olmaktan memnuniyet duyarız.
kzh

4

method_two çalışmaz, çünkü bir üye işlevi tanımlarsınız ancak ona işlevin ne üyesi olduğunu söylemezsiniz. Son satırı yürütürseniz şunları elde edersiniz:

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

Bir sınıfın üye işlevlerini tanımlıyorsanız, ilk argüman her zaman 'benlik' olmalıdır.


3

Yukarıdaki Armin Ronacher'den doğru açıklama, benim gibi yeni başlayanların iyi anlayabilmesi için cevaplarını genişletti:

Bir sınıfta tanımlanan yöntemlerdeki fark, statik veya örnek yöntemi (burada başka bir tür - sınıf yöntemi var - burada tartışılmadan tartışılsın), sınıf örneğine bir şekilde bağlı olup olmadıkları gerçeğinde yatmaktadır. Örneğin, yöntemin çalışma zamanı sırasında sınıf örneğine başvuru alıp almadığını varsayalım

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__Sınıf nesnesi sözlük özelliği böylece tüm özellikleri ve bir sınıf nesnesi yöntem ve referans tutan

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

foo yöntemine yukarıdaki gibi erişilebilir. Burada dikkat edilmesi gereken önemli bir nokta, python'daki her şeyin bir nesne olduğudur ve bu nedenle yukarıdaki sözlükteki referansların kendileri diğer nesnelere işaret ediyor olmasıdır. Onlara Class Property Objects diyeyim - ya da kısalık cevabım kapsamında CPO olarak.

Bir CPO bir tanımlayıcıysa, python yorumlayıcısı __get__()içerdiği değere erişmek için CPO yöntemini çağırır .

Bir CPO'nun tanımlayıcı olup olmadığını belirlemek için, python yorumlayıcısı tanımlayıcı protokolünü uygulayıp uygulamadığını kontrol eder. Tanımlayıcı protokol uygulamak 3 yöntem uygulamaktır

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

örneğin

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

nerede

  • self CPO (liste, str, işlev vb. bir örneği olabilir) ve çalışma zamanı tarafından sağlanır
  • instance bu CPO'nun tanımlandığı sınıfın örneğidir (yukarıdaki 'c' nesnesi) ve tarafımızdan açıklık sağlanması gerekir
  • ownerbu CPO'nun tanımlandığı sınıftır (yukarıdaki 'C' sınıf nesnesi) ve bizim tarafımızdan sağlanması gerekir. Ancak bunun sebebi onu CPO'da çağırıyoruz. bunu örnek üzerinde çağırdığımızda, çalışma zamanının örneği veya sınıfını (polimorfizm) sağlayabileceğinden bunu sağlamamız gerekmez.
  • value CPO için öngörülen değerdir ve tarafımızdan sağlanmalıdır

Tüm CPO'lar tanımlayıcı değildir. Örneğin

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

Bunun nedeni liste sınıfının tanımlayıcı protokolünü uygulamamasıdır.

Bu nedenle, kendi kendine argüman c.foo(self)gereklidir çünkü yöntem imzası aslında budur C.__dict__['foo'].__get__(c, C)(yukarıda açıklandığı gibi, C bulunabileceği veya polimorflaştırılacağı için gerekli değildir) Ve bu da gerekli örnek argümanını geçmezseniz bir TypeError almanızın nedeni de budur.

Yönteme yine de Object C sınıfı üzerinden başvurulduğunu fark ederseniz ve class örneği ile bağlantı, örnek nesne biçimindeki bir bağlamın bu işleve geçirilmesiyle gerçekleştirilir.

Bu, bağlam için herhangi bir bağlamı veya bağlamı korumayı seçerseniz, gereken tek şey, tanımlayıcı CPO'yu sarmak için bir sınıf yazmak ve __get__()bağlam gerektirmeyen yöntemini geçersiz kılmaktı . Bu yeni sınıf, dekoratör olarak adlandırdığımız şeydir ve anahtar kelime ile uygulanır@staticmethod

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

Yeni sarılmış CPO'da bağlamın bulunmaması foobir hata atmaz ve aşağıdaki gibi doğrulanabilir:

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

Statik bir yöntemin kullanılması daha çok bir ad alanı ve kod bakımıdır (bir sınıftan çıkarılması ve modül boyunca kullanılabilir hale getirilmesi vb.).

Yöntemleri bağlamsallaştırmanız gerekmediği sürece (erişim örneği değişkenleri, sınıf değişkenleri vb. Gibi) mümkün olduğunda örnek yöntemleri yerine statik yöntemler yazmak daha iyi olabilir. Bunun bir nedeni, nesnelere istenmeyen başvurular yapmadan çöp toplama işlemini kolaylaştırmaktır.


1

bu bir hatadır.

her şeyden önce, ilk satır böyle olmalıdır (büyük harflere dikkat edin)

class Test(object):

Bir sınıfın yöntemini her çağırdığınızda, kendisini ilk argüman olarak alır (dolayısıyla self adı) ve method_two bu hatayı verir

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

1

İkincisi çalışmaz, çünkü python dahili olarak a_test örneği ile ilk argüman olarak çağırmaya çalışır, ancak method_two herhangi bir argümanı kabul etmez, bu yüzden çalışmaz, bir çalışma zamanı alırsınız. hata. Statik bir yöntemin eşdeğerini istiyorsanız bir sınıf yöntemi kullanabilirsiniz. Python'da sınıf yöntemlerine Java veya C # gibi dillerde statik yöntemlere göre çok daha az ihtiyaç vardır. Çoğu zaman en iyi çözüm, modülde, sınıf tanımının dışında, sınıf yöntemlerinden daha verimli çalışan bir yöntem kullanmaktır.


Bir işlev tanımlasam Class Test(object): @staticmethod def method_two(): print(“called method_two”) Bir kullanım durumu, bu işlevin bir sınıfın parçası olmasını istediğiniz, ancak kullanıcının işleve doğrudan erişmesini istemediğinizi düşündüm. Bu nedenle, örneğin method_twoiçindeki diğer işlevler tarafından çağrılabilir Test, ancak kullanılarak çağrılamaz a_test.method_two(). Kullanırsam def method_two(), bu kullanım durumu için çalışır mı? Veya işlev tanımını değiştirmenin daha iyi bir yolu var, bu yüzden yukarıdaki kullanım durumu için tasarlandığı gibi çalışıyor mu?
alpha_989

1

Method_two çağrısı, Python çalışma zamanının otomatik olarak geçeceği self parametresini kabul etmemek için bir istisna atar.

Bir Python sınıfında statik bir yöntem oluşturmak istiyorsanız, ile süsleyin staticmethod decorator.

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

Test.method_two()


0

Tanımı method_twogeçersiz. Aradığınızda tercümandan method_twoalırsınız TypeError: method_two() takes 0 positional arguments but 1 was given.

Örnek yöntemi, böyle çağırdığınızda sınırlı bir işlevdir a_test.method_two(). selfBir örneğini gösteren Testilk parametre olarak otomatik olarak kabul eder . selfParametre aracılığıyla , bir örnek yöntemi özniteliklere serbestçe erişebilir ve aynı nesne üzerinde bunları değiştirebilir.


0

Bağlı Olmayan Yöntemler

Bağlı olmayan yöntemler, henüz belirli bir sınıf örneğine bağlı olmayan yöntemlerdir.

Bağlı Yöntemler

Bağlı yöntemler, bir sınıfın belirli bir örneğine bağlı yöntemlerdir.

Onun belgelendiği gibi burada , kendini bağlanmamış veya statik, bağlı olduğu işleve bağlı olarak farklı şeyler ifade edebilir.

Aşağıdaki örneğe bir göz atın:

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

Sınıf örneğini kullanmadığımızdan - obj- son çağrıda, bunun statik bir yöntem gibi göründüğünü söyleyebiliriz.

Öyleyse, MyClass.some_method(10)çağrı ile @staticmethoddekoratörle süslenmiş statik bir işleve çağrı arasındaki fark nedir?

Dekoratörü kullanarak, yöntemin öncelikle bir örnek oluşturmadan kullanılacağını açıkça belirtiyoruz. Normalde, sınıf üyesi yöntemlerinin örnek olmadan kullanılmasını beklemez ve bunlara erişmek, yöntemin yapısına bağlı olarak olası hatalara neden olabilir.

Ayrıca, @staticmethoddekoratörü ekleyerek, bir nesneden de ulaşılmasını mümkün kılıyoruz.

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

Yukarıdaki örneği örnek yöntemleriyle yapamazsınız. Sen ilkini hayatta olabilir (daha önce yaptığı gibi) ama ikincisi bir ilişkisiz çağrı çevrilecektir MyClass.some_method(obj, 10)bir yükseltecektir TypeErrorÖrnek yöntemi bir argüman alır ve istemeden iki geçmeye çalıştı beri.

“Ben bir örneği ve bir sınıf hem aracılığıyla statik yöntemleri çağırabilir, eğer Ardından, diyebilirsiniz MyClass.some_static_methodve MyClass().some_static_methodaynı yöntemler olmalıdır.” Evet!


0

Bağlı yöntem = örnek yöntemi

Bağlı olmayan yöntem = statik yöntem.


Lütfen cevabınıza başkalarının da öğrenebilmesi için biraz açıklama ekleyin. Örneğin, bu sorunun diğer cevaplarına bir göz atın
Nico Haase
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.