Câu hỏi Sự khác biệt giữa @staticmethod và @classmethod trong Python là gì?


Sự khác biệt giữa một hàm được trang trí bằng @staticmethod và một trang trí với @classmethod?


2675
2017-09-25 21:01


gốc


phương pháp tĩnh đôi khi tốt hơn là chức năng cấp mô-đun trong python vì lợi ích của sự sạch sẽ. Với chức năng mô-đun, việc nhập khẩu chỉ là chức năng bạn cần và ngăn không cần thiết "." cú pháp (Tôi đang nhìn bạn Mục tiêu-C). các phương thức lớp có nhiều sử dụng hơn vì chúng có thể được sử dụng kết hợp với đa hình để tạo ra 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 như một tham số ngầm định. - FistOfFury
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à các 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à không thay đổi thông qua kế thừa. - imsrgadich


Các câu trả lời:


Có thể một chút mã ví dụ sẽ giúp: Lưu ý sự khác biệt trong chữ ký cuộc gọi của foo, class_foo và static_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ột cá thể đối tượng gọi một phương thức. Ví dụ đối tượng, a, được ngầm truyền như đối số đầu tiên.

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

Với classmethods, lớp của đối tượng đối tượng được ngầm truyền như đố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_foo sử dụng lớp học. Thực tế, nếu bạn định nghĩa một cái gì đó một classmethod, có lẽ vì bạn định gọi nó từ class chứ không phải từ một instance class. A.foo(1) đã nêu ra một 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 các phương pháp lớp học là tạo các nhà thầu thay thế có thể kế thừa.


Với staticmethods, cũng không self (ví dụ đối tượng) cũng không cls (lớp) được ngầm truyền như đối số đầu tiên. Chúng hoạt động giố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 cá thể hoặc một lớp:

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

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

Các staticmethod được sử dụng để nhóm các hàm có một số kết nối logic với một lớp tới lớp.


foo chỉ là một chức năng, nhưng khi bạn gọi a.foo bạn không chỉ nhận được hàm, bạn nhận được một phiên bản "được áp dụng một phần" của hàm với cá thể đối tượng a bị ràng buộc như đối số đầu tiên cho hàm. foo dự kiến ​​2 đối số, trong khi a.foo chỉ mong đợi 1 đối số.

a là ràng buộc để foo. Đó là ý nghĩa của từ "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, a không bị ràng buộc class_foo, đúng hơn là lớp học A là ràng buộc để class_foo.

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

Ở đây, với một staticmethod, mặc dù nó là một phương pháp, a.static_foo chỉ trả lại một hàm 'ole' tốt mà không có đối số nào bị ràng buộc. static_foo dự đoán 1 đối số và a.static_foo cũng dự đoán 1 đối số.

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

Và tất nhiên điều tương tự cũng xảy ra khi bạn gọi static_foo với lớp A thay thế.

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

2320
2017-11-03 19:13



Tôi không hiểu những gì bắt cho việc sử dụng staticmethod. chúng ta chỉ có thể sử dụng một hàm ngoài lớp đơn giản. - Alcott
@Alcott: Bạn có thể muốn di chuyển một hàm vào một lớp bởi vì nó hợp lý thuộc về lớp. Trong mã nguồn Python (ví dụ: multiprocessing, turtle, dist-packages), nó được sử dụng để "ẩn" các chức năng "riêng lẻ" riêng lẻ từ không gian tên module. Mặc dù vậy, việc sử dụng nó được tập trung cao chỉ trong một vài mô-đun - có lẽ là dấu hiệu cho thấy nó chủ yếu là một điều 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, @staticmethodcó thể giúp tổ chức mã của bạn bằng cách bị ghi đè bởi các lớp con. Nếu không có nó, bạn sẽ có các biến thể của hàm nổi trong không gian tên module. - unutbu
... cùng với một số giải thích về Ở đâu và tại sao để sử dụng một trong hai cá thể, 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
@Alcott: như unutbu cho biết, phương pháp tĩnh là một tổ chức / tính năng phong cách. Đôi khi một mô-đun có nhiều lớp và một số hàm 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 đó, có ý nghĩa là không "gây ô nhiễm" mô-đun với nhiều "chức năng 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 nghèo của các lớp trộn và chức năng defs cùng nhau trong mã chỉ để cho thấy chúng là "liên quan" - MestreLion
@unutbu: Tuy nhiên, bạn nên xóa trích dẫn Guido: Đó là từ bản nháp đầu tiên của chú thích phát hành python 2.2 và nó đã được thay đổi trong 2.2.3 thành: However, class methods are still useful in other places, for example, to program inheritable alternate constructors. Ngay cả những trích dẫn ban đầu là rất sai lầm, như ông chỉ nói rằng phân phối python không sử dụng classmethod. Nhưng chỉ vì bản thân python không sử dụng một tính năng nhất định, nó không có nghĩa là nó là một tính năng vô dụng. Nghĩ về số phức hoặc thậm chí sqrt: python chính nó có thể không sử dụng một trong hai, nhưng nó xa là vô dụng để cung cấp - MestreLion


A 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 tiềm ẩn. Về cơ bản nó là vô ích trong Python - bạn chỉ có thể sử dụng một hàm module thay vì một staticmethod.

A classmethod, mặt khác, là một phương thức được thông qua lớp mà nó được gọi, hoặc lớp của cá thể nó được gọi là đối số đầu tiên. Điều này rất hữu ích khi bạn muốn phương thức là một nhà máy cho lớp: vì nó nhận được lớp thực tế nó được gọi là đối số đầu tiên, bạn luôn có thể khởi tạo lớp đúng, ngay cả khi các lớp con có liên quan. Quan sát ví dụ như thế nào dict.fromkeys(), một classmethod, trả về một cá thể 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
>>> 

654
2017-09-25 21:05



Một staticmethod không phải là vô ích - đó là một cách để đặt một hàm vào một lớp (vì nó thuộc về mặt 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
Do đó chỉ 'về cơ bản' vô dụng. Tổ chức như vậy, cũng như tiêm phụ thuộc, là sử dụng hợp lệ các staticmethod, 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 ích của chúng là rất hiếm. - Thomas Wouters
Điều gì là 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 cả lớp hoặc các cá thể của nó? - Ben James
Có lẽ vì lợi ích kế thừa? 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 mẫu và các 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 cá thể, vì vậy sự khác biệt duy nhất giữa lớp và các phương thức tĩnh là đối số đầu tiên ngầm định. - haridsv
Họ cũng tạo ra một không gian tên sạch hơn, và làm cho nó dễ dàng hơn để hiểu chức năng có một cái gì đó để làm với lớp. - Imbrondir


Về cơ bản @classmethod tạo 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à cá thể lớp), @staticmethodkhông có bất kỳ đối số tiềm ẩn nào.


110
2017-09-25 21:07





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

@classmethod

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

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

Các @classmethod biểu mẫu là một hàm    người trang trí - xem mô tả về   định nghĩa hàm trong Chức năng   các định nghĩa để biết chi tiết.

Nó có thể được gọi là trên lớp   (nhu la C.f()) hoặc trên một cá thể   (nhu la C().f()). Ví dụ là   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 dẫn xuất   lớp, đối tượng lớp dẫn xuất là   được thông qua như là đối số đầu tiên ngụ ý.

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

@staticmethod

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

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

Các @staticmethod biểu mẫu là một hàm    người trang trí - xem mô tả về   định nghĩa hàm trong Chức năng   các định nghĩa để biết chi tiết.

Nó có thể được gọi là trên lớp   (nhu la C.f()) hoặc trên một cá thể   (nhu la C().f()). Ví dụ là   bỏ qua ngoại trừ lớp của nó.

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


76
2017-11-03 19:23





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

Hàm @staticmethod không có gì hơn là 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. Đó là định nghĩa là bất biến thông qua kế thừa.

Hàm @classmethod cũng có thể gọi mà không instantiating lớp, nhưng định nghĩa của nó sau lớp Sub, không phải lớp cha, thông qua kế thừa. Đó là vì đối số đầu tiên cho hàm @classmethod phải luôn là các cl (lớp).


55
2017-11-03 19:02



Vì vậy, điều đó có nghĩa rằng bằng cách sử dụng một staticmethod tôi luôn luôn ràng buộc với lớp cha và với classmethod tôi ràng buộc lớp mà tôi tuyên bố classmethod trong (trong trường hợp này là lớp phụ)? - Mohan Gulati
Không. Bằng cách sử dụng staticmethod bạn không bị ràng buộc chút nào; không có tham số đầu tiên tiềm ẩn. Bằng cách sử dụng classmethod, bạn nhận được như là tham số đầu tiên ngầm định của lớp mà bạn gọi là phương thức on (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 on (nếu bạn gọi nó trên một cá thể). - Matt Anderson
Có thể được mở rộng một chút để chỉ ra rằng, bằng việc 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 không (chúng cần phải mã hóa MyClass.attr cho điều đó) - MestreLion


Để quyết định có sử dụng hay không @staticmethod hoặc là @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

44
2018-04-22 15:40



Đáng tiếc là ví dụ phương pháp tĩnh của bạn thực sự làm truy cập biến lớp. Bạn có thể cải thiện ví dụ của bạn, có thể. - Cilyan


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, nó thể hiện chữ ký của các kiểu phương thức khác nhau và cung cấp một chuỗi mã để giải thích mỗi:

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 thể hiện thông thường

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

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 pháp cá thể, join trên chuỗi này, để tham gia một lần lặp khác, nó khá rõ ràng là một hàm của cá thể, ngoài việc là một hàm của danh sách có thể lặp lại, ['a', 'b', 'c']:

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

Phương pháp ràng buộc

Các phương thức thể hiện có thể bị ràng buộc thông qua việc tìm kiếm rải rác để sử dụng sau này.

Ví dụ, điều này liên kết với str.join phương pháp để ':' ví dụ:

>>> join_with_colons = ':'.join 

Và sau đó chúng ta có thể sử dụng nó như một hàm đã có đối số đầu tiên liên kết với nó. Theo cách này, nó hoạt động như một hàm một phần trên cá thể:

>>> 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 pháp tĩnh không không phải lấy ví dụ làm đối số.

Nó rất giống với chức năng mức module.

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

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

Ví dụ về phương pháp tĩnh là str.maketrans, di chuyển từ string module trong Python 3. Nó làm cho một bảng dịch phù hợp cho tiêu thụ bởi 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 nhập hàm từ string mô-đun khá vụng về và thật tuyệt khi có thể gọi nó từ lớp, như trong str.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

Một phương thức lớp là tương tự như một phương thức thể hiện ở chỗ nó lấy một tham số đầu tiên tiềm ẩn, nhưng thay vì lấy dụ, nó sẽ lấy lớp đó. Thường thì chúng được sử dụng như các nhà thầu 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 của phương thức lớp nội trang là dict.fromkeys. Nó được sử dụng như là một nhà xây dựng thay thế của dict, (rất thích hợp cho khi bạn biết những gì các phím của bạn và muốn có một giá trị mặc định cho họ.)

>>> 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 cá thể 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 cho 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 về classmethod và staticmethod.


38
2018-01-23 20:01