Sự khác biệt giữa tĩnh điện và phân loại


3582

Sự khác biệt giữa một chức năng được trang trí với @staticmethodvà một trang trí với là @classmethodgì?


11
Các phương thức tĩnh đôi khi tốt hơn là các hàm cấp mô-đun trong python vì mục đích sạch sẽ. Với chức năng mô-đun, việc nhập chỉ chức năng bạn cần và ngăn chặn "không cần thiết" sẽ không dễ dàng hơn. cú pháp (Tôi đang nhìn bạn Objective-C). các phương thức lớp được sử dụng nhiều hơn vì chúng có thể được sử dụng kết hợp với đa hình để tạo các hàm "mẫu nhà máy". điều này là do các phương thức lớp nhận lớp là một tham số ngầm định.
FistOfFury

27
tl; dr >> khi so sánh với các phương thức bình thường, các phương thức tĩnh và phương thức lớp cũng có thể được truy cập bằng cách sử dụng lớp nhưng không giống như các phương thức lớp, các phương thức tĩnh là bất biến thông qua kế thừa.
imsrgadich

4
Cuộc nói chuyện liên quan của Raymond Hettinger về chủ đề: youtube.com/watch?v=HTLu2DFOdTg
moooeeeep

chính xác hơn youtube.com/watch?v=HTLu2DFOdTg&feature=youtu.be&t=2689 bạn chỉ cần classmethod cho các nhà xây dựng thay thế. Mặt khác, bạn có thể sử dụng staticmethod và truy cập bất kỳ thuộc tính lớp nào (thông qua. / Dot) bằng "ClassNAME" thực tế nhiều thông tin hơn thay vì cls như trong classmethod
Robert Nowak

Câu trả lời:


3137

Có lẽ một chút ví dụ mã sẽ giúp: Thông báo sự khác biệt trong chữ ký cuộc gọi của foo, class_foostatic_foo:

class A(object):
    def foo(self, x):
        print "executing foo(%s, %s)" % (self, x)

    @classmethod
    def class_foo(cls, x):
        print "executing class_foo(%s, %s)" % (cls, x)

    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)" % x    

a = A()

Dưới đây là cách thông thường mà một đối tượng gọi một phương thức. Đối tượng a, được ngầm định chuyển qua làm đối số đầu tiên.

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>,1)

Với classmethods , lớp của thể hiện đối tượng được truyền hoàn toàn dưới dạng đối số đầu tiên thay vì self.

a.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

Bạn cũng có thể gọi class_foobằng cách sử dụng lớp. Trong thực tế, nếu bạn định nghĩa một cái gì đó là một classmethod, thì có lẽ là do bạn có ý định gọi nó từ lớp chứ không phải từ một thể hiện của lớp. A.foo(1)sẽ tăng TypeError, nhưng A.class_foo(1)hoạt động tốt:

A.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)

Một người sử dụng đã tìm thấy cho các phương thức lớp là tạo các hàm tạo thay thế kế thừa .


Với staticmethod , cả self(đối tượng) và cls(lớp) đều không được truyền hoàn toàn làm đối số đầu tiên. Chúng hoạt động như các hàm đơn giản ngoại trừ việc bạn có thể gọi chúng từ một thể hiện hoặc lớp:

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

Tĩnh được sử dụng để nhóm các chức năng có một số kết nối logic với một lớp với lớp.


foochỉ là một hàm, nhưng khi bạn gọi a.foobạn không nhận được hàm, bạn sẽ có được một phiên bản "được áp dụng một phần" của hàm với đối tượng được aràng buộc làm đối số đầu tiên cho hàm. foomong đợi 2 đối số, trong khi a.foochỉ mong đợi 1 đối số.

abị ràng buộc với foo. Đó là những gì có nghĩa của thuật ngữ "ràng buộc" dưới đây:

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

Với a.class_foo, akhông bị ràng buộc class_foo, thay vào đó lớp Abị ràng buộc class_foo.

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

Ở đây, với một phương pháp tĩnh, mặc dù đó là một phương thức, a.static_foochỉ trả về một hàm 'ole tốt mà không bị ràng buộc. static_foomong đợi 1 đối số và cũng a.static_foomong đợi 1 đối số.

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

Và tất nhiên điều tương tự xảy ra khi bạn gọi static_foovới lớp Athay thế.

print(A.static_foo)
# <function static_foo at 0xb7d479cc>

182
Tôi không hiểu những gì bắt được khi sử dụng tĩnh điện. chúng ta chỉ có thể sử dụng một hàm ngoài lớp đơn giản.
Alcott

372
@Alcott: Bạn có thể muốn chuyển một hàm vào một lớp vì nó thuộc về logic. Trong mã nguồn Python (ví dụ: đa xử lý, rùa, gói dist), nó được sử dụng để "ẩn" các hàm "riêng tư" gạch dưới khỏi không gian tên mô-đun. Mặc dù vậy, việc sử dụng nó tập trung cao độ chỉ trong một vài mô-đun - có lẽ là một dấu hiệu cho thấy nó chủ yếu là một thứ phong cách. Mặc dù tôi không thể tìm thấy bất kỳ ví dụ nào về điều này, nhưng @staticmethodcó thể giúp tổ chức mã của bạn bằng cách được ghi đè bởi các lớp con. Không có nó, bạn có các biến thể của hàm trôi nổi trong không gian tên mô-đun.
unutbu

15
... cùng với một số giải thích về vị trílý do sử dụng phương thức tĩnh, lớp hoặc phương thức tĩnh. Bạn đã không đưa ra một từ nào về nó, nhưng OP cũng không hỏi về nó.
MestreLion

106
@Alcott: như unutbu đã nói, các phương thức tĩnh là một tính năng tổ chức / phong cách. Đôi khi một mô-đun có nhiều lớp và một số chức năng của trình trợ giúp được liên kết một cách hợp lý với một lớp đã cho và không phải với các lớp khác, do đó, không nên "làm ô nhiễm" mô-đun có nhiều "hàm miễn phí", và tốt hơn là sử dụng tĩnh phương pháp hơn là dựa vào phong cách pha trộn kém giữa các lớp và hàm defs với nhau trong mã chỉ để cho thấy chúng có "liên quan"
MestreLion

4
Thêm một lần sử dụng cho @staticmethod- bạn có thể sử dụng nó để loại bỏ cruft. Tôi đang triển khai một ngôn ngữ lập trình trong Python - các hàm do thư viện xác định sử dụng một executephương thức tĩnh , trong đó các hàm do người dùng định nghĩa yêu cầu các đối số thể hiện (tức là thân hàm). Trình trang trí này loại bỏ các cảnh báo "tham số không sử dụng" trong thanh tra PyCharm.
tehwalrus 28/03/2015

798

Một staticmethod là một phương thức không biết gì về lớp hoặc cá thể mà nó được gọi. Nó chỉ nhận được các đối số đã được thông qua, không có đối số đầu tiên ngầm định. Về cơ bản, nó là vô dụng trong Python - bạn chỉ có thể sử dụng một chức năng mô-đun thay vì tĩnh.

Mặt khác , classmethod là một phương thức được truyền vào lớp mà nó được gọi hoặc lớp của thể hiện mà nó được gọi, như là đối số đầu tiên. Điều này hữu ích khi bạn muốn phương thức trở thành một nhà máy cho lớp: vì nó nhận được lớp thực tế mà nó được gọi là đối số đầu tiên, bạn luôn có thể khởi tạo đúng lớp, ngay cả khi có các lớp con tham gia. Quan sát ví dụ như thế nào dict.fromkeys(), một lớp đối xứng, trả về một thể hiện của lớp con khi được gọi trên một lớp con:

>>> class DictSubclass(dict):
...     def __repr__(self):
...         return "DictSubclass"
... 
>>> dict.fromkeys("abc")
{'a': None, 'c': None, 'b': None}
>>> DictSubclass.fromkeys("abc")
DictSubclass
>>> 

714
Một tĩnh không phải là vô dụng - đó là một cách để đưa một hàm vào một lớp (vì nó thuộc về logic), trong khi chỉ ra rằng nó không yêu cầu quyền truy cập vào lớp.
Tony Meyer

136
Do đó chỉ "về cơ bản" vô dụng. Tổ chức như vậy, cũng như tiêm phụ thuộc, là các cách sử dụng tĩnh đối xứng hợp lệ, nhưng vì các mô-đun, không phải các lớp như trong Java, là các thành phần cơ bản của tổ chức mã trong Python, việc sử dụng và tính hữu dụng của chúng là rất hiếm.
Thomas Wouters

40
Điều gì hợp lý về việc định nghĩa một phương thức bên trong một lớp, khi nó không liên quan gì đến lớp hoặc các thể hiện của nó?
Ben James

106
Có lẽ vì lợi ích thừa kế? Các phương thức tĩnh có thể được kế thừa và ghi đè giống như các phương thức cá thể và phương thức lớp và việc tra cứu hoạt động như mong đợi (không giống như trong Java). Các phương thức tĩnh không thực sự được giải quyết tĩnh cho dù được gọi trên lớp hay thể hiện, do đó, sự khác biệt duy nhất giữa các phương thức lớp và tĩnh là đối số đầu tiên ẩn.
haridsv

80
Chúng cũng tạo ra một không gian tên sạch hơn và giúp dễ hiểu hơn hàm có liên quan đến lớp.
Imbrondir

149

Về cơ bản @classmethodtạo ra một phương thức có đối số đầu tiên là lớp mà nó được gọi từ (chứ không phải là thể hiện của lớp), @staticmethodkhông có bất kỳ đối số ngầm định nào.


103

Tài liệu python chính thức:

@ classmethod

Một phương thức lớp nhận lớp là đối số đầu tiên ẩn, giống như một phương thức cá thể nhận thể hiện. Để khai báo một phương thức lớp, sử dụng thành ngữ này:

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ... 

Biểu @classmethodmẫu là một trình trang trí hàm - xem mô tả về các định nghĩa hàm trong các định nghĩa hàm để biết chi tiết.

Nó có thể được gọi hoặc trên lớp (chẳng hạn C.f()) hoặc trên một thể hiện (chẳng hạn như C().f()). Ví dụ được bỏ qua ngoại trừ lớp của nó. Nếu một phương thức lớp được gọi cho một lớp dẫn xuất, thì đối tượng lớp dẫn xuất được truyền như là đối số đầu tiên ngụ ý.

Các phương thức lớp khác với các phương thức tĩnh C ++ hoặc Java. Nếu bạn muốn những cái đó, xem staticmethod()trong phần này.

@staticmethod

Một phương thức tĩnh không nhận được một đối số đầu tiên ẩn. Để khai báo một phương thức tĩnh, sử dụng thành ngữ này:

class C:
    @staticmethod
    def f(arg1, arg2, ...): ... 

Biểu @staticmethodmẫu là một trình trang trí hàm - xem mô tả về các định nghĩa hàm trong các định nghĩa hàm để biết chi tiết.

Nó có thể được gọi hoặc trên lớp (chẳng hạn C.f()) hoặc trên một thể hiện (chẳng hạn như C().f()). Ví dụ được bỏ qua ngoại trừ lớp của nó.

Các phương thức tĩnh trong Python tương tự như các phương thức được tìm thấy trong Java hoặc C ++. Đối với một khái niệm nâng cao hơn, xem classmethod()trong phần này.


Không có lỗi trong tài liệu? Không nên ở trạng thái tĩnh: "Cả thể hiện và lớp của nó đều bị bỏ qua." thay vì "Ví dụ được bỏ qua ngoại trừ lớp của nó."?
mirek

Đó có thể là một lỗi cắt và dán, nhưng nói đúng ra bạn không thể gọi một phương thức trên một lớp nếu bạn bỏ qua lớp đó.
Aaron Bentley

76

Đây là một bài viết ngắn về câu hỏi này

Hàm @staticmethod không gì khác hơn là một hàm được định nghĩa bên trong một lớp. Nó có thể gọi được mà không cần khởi tạo lớp trước. Định nghĩa của nó là bất biến thông qua thừa kế.

Hàm @ classmethod cũng có thể gọi được mà không cần khởi tạo lớp, nhưng định nghĩa của nó tuân theo lớp Sub, không phải lớp Parent, thông qua kế thừa. Đó là bởi vì đối số đầu tiên cho hàm @ classmethod phải luôn là cls (class).


1
Vì vậy, điều đó có nghĩa là bằng cách sử dụng tĩnh điện, tôi luôn bị ràng buộc với lớp Parent và với classmethod, tôi bị ràng buộc lớp mà tôi khai báo classmethod trong (trong trường hợp này là lớp con)?
Mohan Gulati

7
Không. Bằng cách sử dụng tĩnh điện, bạn hoàn toàn không bị ràng buộc; không có tham số đầu tiên ngầm định. Bằng cách sử dụng classmethod, bạn nhận được tham số đầu tiên ngầm định lớp mà bạn đã gọi phương thức trên (nếu bạn gọi nó trực tiếp trên một lớp) hoặc lớp của cá thể mà bạn đã gọi phương thức trên (nếu bạn gọi nó là một thể hiện).
Matt Anderson

6
Có thể được mở rộng một chút để cho thấy rằng, bằng cách có một lớp làm đối số đầu tiên, các phương thức lớp có quyền truy cập trực tiếp vào các thuộc tính và phương thức lớp khác, trong khi các phương thức tĩnh thì không (chúng sẽ cần mã hóa MyClass.attr cho điều đó)
MestreLion

"Định nghĩa của nó là bất biến thông qua thừa kế." không có ý nghĩa gì trong Python, bạn có thể ghi đè một phương thức tĩnh tốt.
cz

68

Để quyết định có nên sử dụng @staticmethod hoặc @classmethod bạn phải nhìn vào bên trong phương pháp của bạn. Nếu phương thức của bạn truy cập các biến / phương thức khác trong lớp của bạn thì hãy sử dụng @ classmethod . Mặt khác, nếu phương thức của bạn không chạm vào bất kỳ phần nào khác của lớp thì hãy sử dụng @staticmethod.

class Apple:

    _counter = 0

    @staticmethod
    def about_apple():
        print('Apple is good for you.')

        # note you can still access other member of the class
        # but you have to use the class instance 
        # which is not very nice, because you have repeat yourself
        # 
        # For example:
        # @staticmethod
        #    print('Number of apples have been juiced: %s' % Apple._counter)
        #
        # @classmethod
        #    print('Number of apples have been juiced: %s' % cls._counter)
        #
        #    @classmethod is especially useful when you move your function to other class,
        #       you don't have to rename the class reference 

    @classmethod
    def make_apple_juice(cls, number_of_apples):
        print('Make juice:')
        for i in range(number_of_apples):
            cls._juice_this(i)

    @classmethod
    def _juice_this(cls, apple):
        print('Juicing %d...' % apple)
        cls._counter += 1

điều gì sẽ là lợi thế của classmethod và cls._count so với staticmethod và Apple._count
Robert Nowak

1
cls._countersẽ vẫn là cls._counterngay cả khi mã được đặt trong một lớp khác hoặc tên lớp được thay đổi. Apple._counterlà cụ thể cho Applelớp học; đối với một lớp khác hoặc khi tên lớp được thay đổi, bạn sẽ cần thay đổi lớp được tham chiếu.
kiamlaluno

53

Sự khác biệt giữa @staticmethod và @ classmethod trong Python là gì?

Bạn có thể đã thấy mã Python giống như mã giả này, chứng minh chữ ký của các loại phương thức khác nhau và cung cấp một chuỗi doc để giải thích từng mã:

class Foo(object):

    def a_normal_instance_method(self, arg_1, kwarg_2=None):
        '''
        Return a value that is a function of the instance with its
        attributes, and other arguments such as arg_1 and kwarg2
        '''

    @staticmethod
    def a_static_method(arg_0):
        '''
        Return a value that is a function of arg_0. It does not know the 
        instance or class it is called from.
        '''

    @classmethod
    def a_class_method(cls, arg1):
        '''
        Return a value that is a function of the class and other arguments.
        respects subclassing, it is called with the class it is called from.
        '''

Phương pháp sơ thẩm bình thường

Đầu tiên tôi sẽ giải thích a_normal_instance_method. Đây chính xác được gọi là " phương thức cá thể ". Khi một phương thức cá thể được sử dụng, nó được sử dụng như một hàm một phần (trái ngược với hàm tổng, được xác định cho tất cả các giá trị khi được xem trong mã nguồn), khi được sử dụng, đối số đầu tiên được xác định trước là thể hiện của đối tượng, với tất cả các thuộc tính đã cho của nó. Nó có thể hiện của đối tượng được liên kết với nó và nó phải được gọi từ một thể hiện của đối tượng. Thông thường, nó sẽ truy cập các thuộc tính khác nhau của thể hiện.

Ví dụ: đây là một thể hiện của một chuỗi:

', '

nếu chúng ta sử dụng phương thức cá thể, jointrên chuỗi này, để tham gia một lần lặp khác, thì rõ ràng đó là một chức năng của cá thể, ngoài việc là một chức năng của danh sách lặp , ['a', 'b', 'c']:

>>> ', '.join(['a', 'b', 'c'])
'a, b, c'

Phương pháp giới hạn

Phương pháp sơ thẩm có thể được ràng buộc thông qua một tra cứu chấm để sử dụng sau này.

Ví dụ, điều này liên kết str.joinphương thức với ':'thể hiện:

>>> join_with_colons = ':'.join 

Và sau này chúng ta có thể sử dụng điều này như là một hàm đã có đối số đầu tiên ràng buộc với nó. Theo cách này, nó hoạt động giống như một phần chức năng trên ví dụ:

>>> join_with_colons('abcde')
'a:b:c:d:e'
>>> join_with_colons(['FF', 'FF', 'FF', 'FF', 'FF', 'FF'])
'FF:FF:FF:FF:FF:FF'

Phương pháp tĩnh

Phương thức tĩnh không lấy ví dụ làm đối số.

Nó rất giống với chức năng cấp mô-đun.

Tuy nhiên, chức năng cấp mô-đun phải sống trong mô-đun và được nhập đặc biệt vào những nơi khác được sử dụng.

Tuy nhiên, nếu nó được gắn vào đối tượng, nó cũng sẽ đi theo đối tượng một cách thuận tiện thông qua việc nhập và kế thừa.

Một ví dụ về phương thức tĩnh là str.maketrans, được chuyển từ stringmô-đun trong Python 3. Nó làm cho bảng dịch phù hợp với mức tiêu thụ str.translate. Nó có vẻ khá ngớ ngẩn khi được sử dụng từ một thể hiện của một chuỗi, như được trình bày dưới đây, nhưng việc nhập hàm từ stringmô-đun khá vụng về và thật tuyệt khi có thể gọi nó từ lớp, như trongstr.maketrans

# demonstrate same function whether called from instance or not:
>>> ', '.maketrans('ABC', 'abc')
{65: 97, 66: 98, 67: 99}
>>> str.maketrans('ABC', 'abc')
{65: 97, 66: 98, 67: 99}

Trong python 2, bạn phải nhập hàm này từ mô đun chuỗi ngày càng ít hữu ích:

>>> import string
>>> 'ABCDEFG'.translate(string.maketrans('ABC', 'abc'))
'abcDEFG'

Phương pháp lớp học

Một phương thức lớp tương tự như một phương thức cá thể ở chỗ nó lấy một đối số đầu tiên ẩn, nhưng thay vì lấy cá thể, nó lấy lớp. Thường thì chúng được sử dụng như các hàm tạo thay thế để sử dụng ngữ nghĩa tốt hơn và nó sẽ hỗ trợ kế thừa.

Ví dụ kinh điển nhất về phân loại dựng sẵn là dict.fromkeys. Nó được sử dụng như một hàm tạo thay thế của dict, (rất phù hợp khi bạn biết khóa của mình là gì và muốn có giá trị mặc định cho chúng.)

>>> dict.fromkeys(['a', 'b', 'c'])
{'c': None, 'b': None, 'a': None}

Khi chúng ta phân lớp dict, chúng ta có thể sử dụng cùng một hàm tạo, tạo ra một thể hiện của lớp con.

>>> class MyDict(dict): 'A dict subclass, use to demo classmethods'
>>> md = MyDict.fromkeys(['a', 'b', 'c'])
>>> md
{'a': None, 'c': None, 'b': None}
>>> type(md)
<class '__main__.MyDict'>

Xem mã nguồn gấu trúc để biết các ví dụ tương tự khác của các nhà xây dựng thay thế và xem thêm tài liệu Python chính thức trên classmethodstaticmethod.


43

Tôi bắt đầu học ngôn ngữ lập trình với C ++, rồi Java và Python và vì vậy câu hỏi này cũng làm phiền tôi rất nhiều, cho đến khi tôi hiểu cách sử dụng đơn giản của từng loại.

Phương thức lớp: Python không giống như Java và C ++ không có quá tải hàm tạo. Và vì vậy để đạt được điều này bạn có thể sử dụng classmethod. Ví dụ sau đây sẽ giải thích điều này

Chúng ta hãy xem xét chúng ta có một Personlớp mà phải mất hai đối số first_namelast_namevà tạo ra các thể hiện của Person.

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

Bây giờ, nếu yêu cầu xuất hiện khi bạn cần tạo một lớp chỉ bằng một tên duy nhất, chỉ cần một first_name, bạn không thể làm điều gì đó như thế này trong Python.

Điều này sẽ cung cấp cho bạn một lỗi khi bạn sẽ cố gắng tạo một đối tượng (ví dụ).

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def __init__(self, first_name):
        self.first_name = first_name

Tuy nhiên, bạn có thể đạt được điều tương tự bằng cách sử dụng @classmethodnhư được đề cập dưới đây

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @classmethod
    def get_person(cls, first_name):
        return cls(first_name, "")

Phương thức tĩnh: Điều này khá đơn giản, nó không bị ràng buộc với thể hiện hoặc lớp và bạn có thể chỉ cần gọi nó bằng tên lớp.

Vì vậy, giả sử trong ví dụ trên bạn cần xác thực first_namekhông quá 20 ký tự, bạn chỉ cần làm điều này.

@staticmethod  
def validate_name(name):
    return len(name) <= 20

và bạn chỉ có thể gọi bằng cách sử dụng class name

Person.validate_name("Gaurang Shah")

2
Đó là một bài viết cũ, nhưng cách pythonic hơn để đạt được hàm tạo chấp nhận một hoặc hai đối số sẽ được sử dụng def __init__(self, first_name, last_name="")thay vì phân loại get_person. Ngoài ra kết quả sẽ hoàn toàn giống nhau trong trường hợp này.
akarilimano

31

Tôi nghĩ một câu hỏi hay hơn là "Khi nào bạn sẽ sử dụng @ classmethod vs @staticmethod?"

@ classmethod cho phép bạn dễ dàng truy cập vào các thành viên riêng có liên quan đến định nghĩa lớp. đây là một cách tuyệt vời để thực hiện các singletons hoặc các lớp xuất xưởng kiểm soát số lượng phiên bản của các đối tượng được tạo tồn tại.

@staticmethod cung cấp mức tăng hiệu suất cận biên, nhưng tôi vẫn chưa thấy việc sử dụng hiệu quả một phương thức tĩnh trong một lớp không thể đạt được như một hàm độc lập bên ngoài lớp.


31

@decorators đã được thêm vào python 2.4 Nếu bạn đang sử dụng python <2.4, bạn có thể sử dụng hàm classmethod () và staticmethod ().

Ví dụ, nếu bạn muốn tạo một phương thức nhà máy (Một hàm trả về một thể hiện của một lớp khác tùy thuộc vào đối số mà nó nhận được), bạn có thể làm một cái gì đó như:

class Cluster(object):

    def _is_cluster_for(cls, name):
        """
        see if this class is the cluster with this name
        this is a classmethod
        """ 
        return cls.__name__ == name
    _is_cluster_for = classmethod(_is_cluster_for)

    #static method
    def getCluster(name):
        """
        static factory method, should be in Cluster class
        returns a cluster object for the given name
        """
        for cls in Cluster.__subclasses__():
            if cls._is_cluster_for(name):
                return cls()
    getCluster = staticmethod(getCluster)

Cũng lưu ý rằng đây là một ví dụ tốt cho việc sử dụng phương thức phân loại và phương thức tĩnh, Phương thức tĩnh rõ ràng thuộc về lớp, vì nó sử dụng lớp Cluster bên trong. Classmethod chỉ cần thông tin về lớp và không có phiên bản của đối tượng.

Một lợi ích khác của việc biến _is_cluster_forphương thức thành một lớp là để một lớp con có thể quyết định thay đổi cách triển khai của nó, có thể vì nó khá chung chung và có thể xử lý nhiều loại cụm, vì vậy chỉ cần kiểm tra tên của lớp là không đủ.


28

Phương pháp tĩnh:

  • Các hàm đơn giản không có đối số tự.
  • Làm việc trên các thuộc tính lớp; không thuộc tính ví dụ.
  • Có thể được gọi thông qua cả lớp và thể hiện.
  • Hàm staticmethod () tích hợp được sử dụng để tạo chúng.

Lợi ích của phương pháp tĩnh:

  • Nó bản địa hóa tên hàm trong classscope
  • Nó di chuyển mã chức năng gần hơn với nơi nó được sử dụng
  • Thuận tiện hơn để nhập so với các hàm cấp mô-đun vì mỗi phương thức không phải nhập đặc biệt

    @staticmethod
    def some_static_method(*args, **kwds):
        pass

Phương pháp lớp học:

  • Các hàm có đối số đầu tiên là tên lớp.
  • Có thể được gọi thông qua cả lớp và thể hiện.
  • Chúng được tạo ra với chức năng classmethod in-build.

     @classmethod
     def some_class_method(cls, *args, **kwds):
         pass

22

@staticmethodchỉ vô hiệu hóa chức năng mặc định như mô tả phương thức. classmethod kết thúc chức năng của bạn trong một thùng chứa có thể gọi được, chuyển một tham chiếu đến lớp sở hữu làm đối số đầu tiên:

>>> class C(object):
...  pass
... 
>>> def f():
...  pass
... 
>>> staticmethod(f).__get__(None, C)
<function f at 0x5c1cf0>
>>> classmethod(f).__get__(None, C)
<bound method type.f of <class '__main__.C'>>

Như một vấn đề thực tế, classmethodcó một chi phí thời gian chạy nhưng làm cho nó có thể truy cập lớp sở hữu. Ngoài ra, tôi khuyên bạn nên sử dụng siêu dữ liệu và đặt các phương thức lớp trên siêu dữ liệu đó:

>>> class CMeta(type):
...  def foo(cls):
...   print cls
... 
>>> class C(object):
...  __metaclass__ = CMeta
... 
>>> C.foo()
<class '__main__.C'>

1
Một nhược điểm có thể có của siêu dữ liệu cho điều này ngay lập tức xảy ra với tôi là bạn không thể gọi trực tiếp lớp đối tượng. c = C(); c.foo()tăng AttributionError, bạn phải làm type(c).foo(). Đây cũng có thể được coi là một tính năng - tôi không thể nghĩ tại sao bạn muốn.
Aaron Hall

20

Hướng dẫn dứt khoát về cách sử dụng các phương thức tĩnh, lớp hoặc trừu tượng trong Python là một liên kết tốt cho chủ đề này và tóm tắt nó như sau.

@staticmethodhàm không có gì khác hơn là một hàm được định nghĩa bên trong một lớp. Nó có thể gọi được mà không cần khởi tạo lớp trước. Định nghĩa của nó là bất biến thông qua thừa kế.

  • Python không phải khởi tạo một phương thức ràng buộc cho đối tượng.
  • Nó làm giảm khả năng đọc của mã và nó không phụ thuộc vào trạng thái của chính đối tượng;

@classmethodHàm cũng có thể gọi được mà không cần khởi tạo lớp, nhưng định nghĩa của nó theo lớp Sub, không phải lớp Parent, thông qua kế thừa, có thể được ghi đè bởi lớp con. Đó là bởi vì đối số đầu tiên cho @classmethodhàm phải luôn là cls (class).

  • Phương pháp nhà máy , được sử dụng để tạo một thể hiện cho một lớp bằng cách sử dụng ví dụ như một số loại tiền xử lý.
  • Các phương thức tĩnh gọi các phương thức tĩnh : nếu bạn tách một phương thức tĩnh trong một số phương thức tĩnh, bạn không nên mã hóa tên lớp mà sử dụng các phương thức lớp

Cảm ơn @zangw - tính bất biến được kế thừa của hàm tĩnh là sự khác biệt chính có vẻ như
hard_work_ant

18

Chỉ đối số đầu tiên khác nhau :

  • Phương thức bình thường: đối tượng hiện tại nếu được tự động chuyển qua làm đối số đầu tiên (bổ sung)
  • classmethod: lớp của đối tượng hiện tại được tự động chuyển qua dưới dạng đối số nắm tay (bổ sung)
  • staticmethod: không có đối số phụ được tự động thông qua. Những gì bạn đã chuyển đến chức năng là những gì bạn nhận được.

Chi tiết hơn...

phương pháp bình thường

Khi phương thức của một đối tượng được gọi, nó sẽ tự động được cung cấp thêm một đối số selflàm đối số đầu tiên. Đó là, phương pháp

def f(self, x, y)

phải được gọi với 2 đối số. selfđược tự động thông qua, và nó là chính đối tượng .

phương pháp lớp

Khi phương pháp được trang trí

@classmethod
def f(cls, x, y)

đối số được cung cấp tự động không phải self , nhưng lớp self .

phương pháp tĩnh

Khi phương pháp được trang trí

@staticmethod
def f(x, y)

phương thức này không được đưa ra bất kỳ đối số tự động nào cả. Nó chỉ được đưa ra các tham số mà nó được gọi với.

tập quán

  • classmethod chủ yếu được sử dụng cho các nhà xây dựng thay thế.
  • staticmethodkhông sử dụng trạng thái của đối tượng. Nó có thể là một chức năng bên ngoài một lớp. Nó chỉ đặt bên trong lớp để nhóm các hàm có chức năng tương tự (ví dụ, như Mathcác phương thức tĩnh của lớp Java )
class Point
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def frompolar(cls, radius, angle):
        """The `cls` argument is the `Point` class itself"""
        return cls(radius * cos(angle), radius * sin(angle))

    @staticmethod
    def angle(x, y):
        """this could be outside the class, but we put it here 
just because we think it is logically related to the class."""
        return atan(y, x)


p1 = Point(3, 2)
p2 = Point.frompolar(3, pi/4)

angle = Point.angle(3, 2)

17

Trước tiên, hãy để tôi nói sự giống nhau giữa một phương thức được trang trí bằng @ classmethod so với @staticmethod.

Tương tự: Cả hai đều có thể được gọi trên chính Class , thay vì chỉ là thể hiện của lớp. Vì vậy, cả hai theo một nghĩa nào đó là phương thức của Class .

Sự khác biệt: Một lớp đối xứng sẽ nhận chính lớp đó là đối số đầu tiên, trong khi một đối xứng tĩnh thì không.

Vì vậy, một phương thức tĩnh, theo một nghĩa nào đó, không bị ràng buộc với chính Class và chỉ bị treo ở đó chỉ vì nó có thể có chức năng liên quan.

>>> class Klaus:
        @classmethod
        def classmthd(*args):
            return args

        @staticmethod
        def staticmthd(*args):
            return args

# 1. Call classmethod without any arg
>>> Klaus.classmthd()  
(__main__.Klaus,)  # the class gets passed as the first argument

# 2. Call classmethod with 1 arg
>>> Klaus.classmthd('chumma')
(__main__.Klaus, 'chumma')

# 3. Call staticmethod without any arg
>>> Klaus.staticmthd()  
()

# 4. Call staticmethod with 1 arg
>>> Klaus.staticmthd('chumma')
('chumma',)

11

Một sự xem xét khác liên quan đến staticmethod vs classmethod đi kèm với sự kế thừa. Giả sử bạn có lớp sau:

class Foo(object):
    @staticmethod
    def bar():
        return "In Foo"

Và sau đó bạn muốn ghi đè bar()trong một lớp con:

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"

Điều này hoạt động, nhưng lưu ý rằng bây giờ việc bar()triển khai trong lớp con ( Foo2) không còn có thể tận dụng bất cứ điều gì cụ thể cho lớp đó. Ví dụ: giả sử Foo2có một phương thức được gọi là magic()bạn muốn sử dụng để Foo2triển khai bar():

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"
    @staticmethod
    def magic():
        return "Something useful you'd like to use in bar, but now can't" 

Cách giải quyết ở đây sẽ được gọi Foo2.magic()trong bar(), nhưng sau đó bạn đang lặp lại chính mình (nếu tên của Foo2những thay đổi, bạn sẽ phải nhớ để cập nhật rằng bar()phương pháp).

Đối với tôi, đây là một sự vi phạm nhẹ đối với nguyên tắc mở / đóng , vì một quyết định được đưa ra Foođang ảnh hưởng đến khả năng của bạn để cấu trúc lại mã phổ biến trong một lớp dẫn xuất (nghĩa là nó ít mở rộng hơn cho phần mở rộng). Nếu bar()là một classmethodchúng tôi sẽ ổn:

class Foo(object):
    @classmethod
    def bar(cls):
        return "In Foo"

class Foo2(Foo):
    @classmethod
    def bar(cls):
        return "In Foo2 " + cls.magic()
    @classmethod
    def magic(cls):
        return "MAGIC"

print Foo2().bar()

Cung cấp: In Foo2 MAGIC


7

Tôi sẽ cố gắng giải thích sự khác biệt cơ bản bằng một ví dụ.

class A(object):
    x = 0

    def say_hi(self):
        pass

    @staticmethod
    def say_hi_static():
        pass

    @classmethod
    def say_hi_class(cls):
        pass

    def run_self(self):
        self.x += 1
        print self.x # outputs 1
        self.say_hi()
        self.say_hi_static()
        self.say_hi_class()

    @staticmethod
    def run_static():
        print A.x  # outputs 0
        # A.say_hi() #  wrong
        A.say_hi_static()
        A.say_hi_class()

    @classmethod
    def run_class(cls):
        print cls.x # outputs 0
        # cls.say_hi() #  wrong
        cls.say_hi_static()
        cls.say_hi_class()

1 - chúng ta có thể gọi trực tiếp tĩnh và phân loại mà không cần khởi tạo

# A.run_self() #  wrong
A.run_static()
A.run_class()

2- Phương thức tĩnh không thể gọi phương thức tự nhưng có thể gọi phương thức tĩnh và phân loại khác

3- Phương thức tĩnh thuộc về lớp và sẽ không sử dụng đối tượng nào cả.

4- Phương thức lớp không bị ràng buộc với một đối tượng mà với một lớp.


quảng cáo 2: Bạn có chắc không? Làm thế nào tĩnh có thể gọi classmethod? Nó không có tham chiếu đến nó (đến lớp của nó).
mirek

7

@ classmethod: có thể được sử dụng để tạo quyền truy cập chung cho tất cả các phiên bản được tạo của lớp đó ..... như cập nhật một bản ghi bởi nhiều người dùng .... Tôi cũng thấy nó sử dụng ưu tiên khi tạo singletons ..: )

Phương thức @static: không liên quan gì đến lớp hoặc cá thể được liên kết với ... nhưng để dễ đọc có thể sử dụng phương thức tĩnh


5

Bạn có thể muốn xem xét sự khác biệt giữa:

Class A:
    def foo():  # no self parameter, no decorator
        pass

Class B:
    @staticmethod
    def foo():  # no self parameter
        pass

Điều này đã thay đổi giữa python2 và python3:

trăn2:

>>> A.foo()
TypeError
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()

trăn3:

>>> A.foo()
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()

Vì vậy, sử dụng @staticmethodcho các phương thức chỉ được gọi trực tiếp từ lớp đã trở thành tùy chọn trong python3. Nếu bạn muốn gọi chúng từ cả lớp và thể hiện, bạn vẫn cần sử dụng trình @staticmethodtrang trí.

Các trường hợp khác đã được bảo hiểm tốt bởi câu trả lời unutbus.


5

Một phương thức lớp nhận lớp là đối số đầu tiên ẩn, giống như một phương thức cá thể nhận thể hiện. Nó là một phương thức được liên kết với lớp chứ không phải đối tượng của lớp. Nó có quyền truy cập vào trạng thái của lớp vì nó lấy một tham số lớp trỏ đến lớp chứ không phải đối tượng. Nó có thể sửa đổi một trạng thái lớp sẽ áp dụng trên tất cả các thể hiện của lớp. Ví dụ, nó có thể sửa đổi một biến lớp sẽ được áp dụng cho tất cả các thể hiện.

Mặt khác, một phương thức tĩnh không nhận được một đối số đầu tiên ngầm định, so với các phương thức lớp hoặc phương thức cá thể. Và không thể truy cập hoặc sửa đổi trạng thái lớp. Nó chỉ thuộc về lớp vì theo quan điểm thiết kế đó là cách chính xác. Nhưng về mặt chức năng không bị ràng buộc, trong thời gian chạy, với lớp.

như một hướng dẫn, sử dụng các phương thức tĩnh làm tiện ích, sử dụng các phương thức lớp, ví dụ như nhà máy. Hoặc có thể để xác định một singleton. Và sử dụng các phương thức thể hiện để mô hình hóa trạng thái và hành vi của các thể hiện.

Hy vọng tôi đã rõ ràng!


4

Đóng góp của tôi chứng minh sự khác biệt giữa @classmethod, @staticmethodvà các phương thức cá thể, bao gồm cả cách một cá thể có thể gián tiếp gọi a @staticmethod. Nhưng thay vì gọi gián tiếp @staticmethodtừ một cá thể, làm cho nó riêng tư có thể là "pythonic" hơn. Lấy một cái gì đó từ một phương thức riêng tư không được thể hiện ở đây nhưng về cơ bản đó là cùng một khái niệm.

#!python3

from os import system
system('cls')
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

class DemoClass(object):
    # instance methods need a class instance and
    # can access the instance through 'self'
    def instance_method_1(self):
        return 'called from inside the instance_method_1()'

    def instance_method_2(self):
        # an instance outside the class indirectly calls the static_method
        return self.static_method() + ' via instance_method_2()'

    # class methods don't need a class instance, they can't access the
    # instance (self) but they have access to the class itself via 'cls'
    @classmethod
    def class_method(cls):
        return 'called from inside the class_method()'

    # static methods don't have access to 'cls' or 'self', they work like
    # regular functions but belong to the class' namespace
    @staticmethod
    def static_method():
        return 'called from inside the static_method()'
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# works even if the class hasn't been instantiated
print(DemoClass.class_method() + '\n')
''' called from inside the class_method() '''

# works even if the class hasn't been instantiated
print(DemoClass.static_method() + '\n')
''' called from inside the static_method() '''
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# >>>>> all methods types can be called on a class instance <<<<<
# instantiate the class
democlassObj = DemoClass()

# call instance_method_1()
print(democlassObj.instance_method_1() + '\n')
''' called from inside the instance_method_1() '''

# # indirectly call static_method through instance_method_2(), there's really no use
# for this since a @staticmethod can be called whether the class has been
# instantiated or not
print(democlassObj.instance_method_2() + '\n')
''' called from inside the static_method() via instance_method_2() '''

# call class_method()
print(democlassObj.class_method() + '\n')
'''  called from inside the class_method() '''

# call static_method()
print(democlassObj.static_method())
''' called from inside the static_method() '''

"""
# whether the class is instantiated or not, this doesn't work
print(DemoClass.instance_method_1() + '\n')
'''
TypeError: TypeError: unbound method instancemethod() must be called with
DemoClass instance as first argument (got nothing instead)
'''
"""

2

Các phương thức lớp, như tên cho thấy, được sử dụng để thực hiện các thay đổi cho các lớp chứ không phải các đối tượng. Để thay đổi các lớp, họ sẽ sửa đổi các thuộc tính lớp (không phải thuộc tính đối tượng), vì đó là cách bạn cập nhật các lớp. Đây là lý do mà các phương thức lớp lấy lớp (được quy ước là 'cls') làm đối số đầu tiên.

class A(object):
    m=54

    @classmethod
    def class_method(cls):
        print "m is %d" % cls.m

Mặt khác, các phương thức tĩnh được sử dụng để thực hiện các chức năng không bị ràng buộc với lớp tức là chúng sẽ không đọc hoặc ghi các biến lớp. Do đó, các phương thức tĩnh không lấy các lớp làm đối số. Chúng được sử dụng để các lớp có thể thực hiện các chức năng không liên quan trực tiếp đến mục đích của lớp.

class X(object):
    m=54 #will not be referenced

    @staticmethod
    def static_method():
        print "Referencing/calling a variable or function outside this class. E.g. Some global variable/function."

2

Phân tích @staticmethod theo nghĩa đen cung cấp những hiểu biết khác nhau.

Một phương thức bình thường của một lớp là một phương thức động ngầm định lấy thể hiện làm đối số đầu tiên.
Ngược lại, một tĩnh đối xứng không lấy ví dụ làm đối số đầu tiên, vì vậy được gọi là 'tĩnh' .

Một tĩnh điện thực sự là một hàm bình thường giống như các hàm ngoài định nghĩa lớp.
Nó may mắn được nhóm vào lớp chỉ để đứng gần hơn nơi nó được áp dụng, hoặc bạn có thể cuộn xung quanh để tìm thấy nó.


2

Tôi nghĩ rằng việc đưa ra một phiên bản Python hoàn toàn staticmethodclassmethod sẽ giúp hiểu được sự khác biệt giữa chúng ở cấp độ ngôn ngữ.

Cả hai đều là mô tả không dữ liệu (Sẽ dễ hiểu hơn nếu bạn quen thuộc với mô tả trước).

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f


class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        def inner(*args, **kwargs):
            if cls is None:
                cls = type(obj)
            return self.f(cls, *args, **kwargs)
        return inner

1

staticmethod không có quyền truy cập vào attibutes của đối tượng, của lớp hoặc của các lớp cha trong hệ thống phân cấp thừa kế. Nó có thể được gọi trực tiếp tại lớp (mà không tạo đối tượng).

classmethod không có quyền truy cập vào các thuộc tính của đối tượng. Tuy nhiên, nó có thể truy cập các thuộc tính của lớp và của các lớp cha trong hệ thống phân cấp thừa kế. Nó có thể được gọi trực tiếp tại lớp (mà không tạo đối tượng). Nếu được gọi tại đối tượng thì nó giống như phương thức bình thường không truy cập self.<attribute(s)>và truy cậpself.__class__.<attribute(s)> chỉ .

Hãy nghĩ rằng chúng ta có một lớp với b=2, chúng ta sẽ tạo một đối tượng và đặt lại b=4nó vào trong đó. Tĩnh không thể truy cập bất cứ điều gì từ trước đó. Classmethod chỉ có thể truy cập .b==2, thông qua cls.b. Phương pháp bình thường có thể truy cập cả: .b==4thông qua self.b.b==2thông qua self.__class__.b.

Chúng ta có thể theo kiểu KISS (giữ cho nó đơn giản, ngu ngốc): Không sử dụng tĩnh và phân loại, không sử dụng các lớp mà không khởi tạo chúng, chỉ truy cập các thuộc tính của đối tượng self.attribute(s). Có những ngôn ngữ mà OOP được thực hiện theo cách đó và tôi nghĩ đó không phải là ý tưởng tồi. :)


Một điều quan trọng nữa đối với phân loại: Nếu bạn sửa đổi một thuộc tính trong phương thức lớp, tất cả các đối tượng hiện có của lớp này không được đặt rõ ràng thuộc tính này sẽ có giá trị được sửa đổi.
mirek

-4

Việc hack nhanh các phương thức giống hệt nhau trong iPython cho thấy @staticmethodmang lại hiệu suất tăng biên (tính bằng nano giây), nhưng nếu không thì nó dường như không phục vụ chức năng. Ngoài ra, mọi hiệu suất tăng có thể sẽ bị xóa sổ bởi công việc bổ sung xử lý phương thức thông quastaticmethod() trong quá trình biên dịch (xảy ra trước bất kỳ thực thi mã nào khi bạn chạy tập lệnh).

Vì mục đích dễ đọc mã, tôi sẽ tránh @staticmethodtrừ khi phương pháp của bạn sẽ được sử dụng cho vô số công việc, trong đó tính bằng nano giây.


7
"Mặt khác dường như không phục vụ chức năng": không hoàn toàn đúng. Xem thảo luận ở trên.
Keith Pinson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.