Sự khác biệt giữa một chức năng được trang trí với @staticmethod
và một trang trí với là @classmethod
gì?
Sự khác biệt giữa một chức năng được trang trí với @staticmethod
và một trang trí với là @classmethod
gì?
Câu trả lời:
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_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à 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_foo
bằ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.
foo
chỉ là một hàm, nhưng khi bạn gọi a.foo
bạ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 a
ràng buộc làm đối số đầu tiên cho hàm. foo
mong đợi 2 đối số, trong khi a.foo
chỉ mong đợi 1 đối số.
a
bị 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
, a
không bị ràng buộc class_foo
, thay vào đó lớp A
bị 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_foo
chỉ trả về một hàm 'ole tốt mà không bị ràng buộc. static_foo
mong đợi 1 đối số và cũng
a.static_foo
mong đợ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_foo
với lớp A
thay thế.
print(A.static_foo)
# <function static_foo at 0xb7d479cc>
@staticmethod
có 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.
@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 execute
phươ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.
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
>>>
Về cơ bản @classmethod
tạ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), @staticmethod
không có bất kỳ đối số ngầm định nào.
Tài liệu python chính thức:
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
@classmethod
mẫ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.
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
@staticmethod
mẫ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.
Đâ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).
Để 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
cls._counter
sẽ vẫn là cls._counter
ngay cả khi mã được đặt trong một lớp khác hoặc tên lớp được thay đổi. Apple._counter
là cụ thể cho Apple
lớ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.
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.
'''
Đầ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ể, join
trê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 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.join
phươ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 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ừ string
mô-đ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ừ string
mô-đ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'
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 classmethod
và staticmethod
.
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 Person
lớp mà phải mất hai đối số first_name
và last_name
và 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 @classmethod
như đượ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_name
khô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")
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.
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.
@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_for
phươ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 đủ.
Phương pháp tĩnh:
Lợi ích của phương pháp tĩnh:
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:
Chúng được tạo ra với chức năng classmethod in-build.
@classmethod
def some_class_method(cls, *args, **kwds):
pass
@staticmethod
chỉ 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ế, classmethod
có 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'>
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.
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.
@staticmethod
hà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ế.
@classmethod
Hà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 @classmethod
hàm phải luôn là cls (class).
Chỉ đối số đầu tiên khác nhau :
Chi tiết hơn...
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ố self
là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 .
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
.
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.
classmethod
chủ yếu được sử dụng cho các nhà xây dựng thay thế. staticmethod
khô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ư Math
cá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)
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',)
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ử Foo2
có một phương thức được gọi là magic()
bạn muốn sử dụng để Foo2
triể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 Foo2
nhữ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 classmethod
chú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
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.
@ 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
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
và
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 @staticmethod
cho 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 @staticmethod
trang trí.
Các trường hợp khác đã được bảo hiểm tốt bởi câu trả lời unutbus.
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!
Đóng góp của tôi chứng minh sự khác biệt giữa @classmethod
, @staticmethod
và 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 @staticmethod
từ 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)
'''
"""
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."
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ó.
Tôi nghĩ rằng việc đưa ra một phiên bản Python hoàn toàn staticmethod
vàclassmethod
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
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=4
nó 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==4
thông qua self.b
và .b==2
thô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. :)
Việc hack nhanh các phương thức giống hệt nhau trong iPython cho thấy @staticmethod
mang 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 @staticmethod
trừ 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.