อะไรคือความแตกต่างระหว่างฟังก์ชั่นที่ตกแต่งด้วย@staticmethod
และฟังก์ชั่นที่ตกแต่งด้วย@classmethod
?
อะไรคือความแตกต่างระหว่างฟังก์ชั่นที่ตกแต่งด้วย@staticmethod
และฟังก์ชั่นที่ตกแต่งด้วย@classmethod
?
คำตอบ:
บางทีโค้ดตัวอย่างเล็กน้อยจะช่วย: สังเกตเห็นความแตกต่างในลายเซ็นการโทรของ foo
, class_foo
และ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()
ด้านล่างเป็นวิธีปกติที่วัตถุอินสแตนซ์เรียกใช้เมธอด ตัวอย่างวัตถุa
ถูกส่งผ่านโดยปริยายเป็นอาร์กิวเมนต์แรก
a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>,1)
ด้วยวิธีการในชั้นเรียนself
ชั้นเช่นวัตถุที่ถูกส่งผ่านไปโดยปริยายเป็นอาร์กิวเมนต์แรกแทน
a.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)
นอกจากนี้คุณยังสามารถโทร class_foo
โดยใช้คลาส ในความเป็นจริงหากคุณกำหนดบางสิ่งบางอย่างให้เป็นวิธีการในชั้นเรียนอาจเป็นเพราะคุณตั้งใจจะโทรจากชั้นเรียนแทนจากชั้นเรียนอินสแตนซ์ A.foo(1)
จะยก TypeError แต่A.class_foo(1)
ทำงานได้ดี:
A.class_foo(1)
# executing class_foo(<class '__main__.A'>,1)
มีผู้ใช้หนึ่งคนที่ค้นพบวิธีการเรียนคือการสร้างตัวสร้างทางเลือกที่สืบทอดได้
ด้วยวิธีการแบบคงที่ทั้งself
(อินสแตนซ์ของออบเจ็กต์) หรือ cls
(คลาส) ที่ส่งผ่านโดยนัยเป็นอาร์กิวเมนต์แรก พวกมันทำตัวเหมือนฟังก์ชั่นธรรมดายกเว้นว่าคุณสามารถเรียกพวกมันจากอินสแตนซ์หรือคลาส:
a.static_foo(1)
# executing static_foo(1)
A.static_foo('hi')
# executing static_foo(hi)
Staticmethods ใช้เพื่อจัดกลุ่มฟังก์ชันที่มีการเชื่อมต่อแบบลอจิคัลกับคลาสถึงคลาส
foo
เป็นเพียงฟังก์ชั่น แต่เมื่อคุณโทรหาa.foo
คุณไม่เพียงแค่รับฟังก์ชั่นคุณจะได้รับฟังก์ชั่นรุ่น "นำไปใช้บางส่วน" ด้วยอินสแตนซ์ของวัตถุที่a
ถูกผูกไว้เป็นอาร์กิวเมนต์แรกของฟังก์ชันfoo
คาดว่าจะมี 2 ข้อโต้แย้งในขณะที่a.foo
คาดว่าจะมีเพียง 1 ข้อเท่านั้น
a
ถูกผูกไว้กับ foo
ถูกผูกไว้กับนั่นคือสิ่งที่มีความหมายโดยคำว่า "ผูกพัน" ด้านล่าง:
print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>
ด้วยa.class_foo
, a
ไม่ผูกพันที่จะclass_foo
ค่อนข้างชั้นถูกผูกไว้กับA
class_foo
print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>
ที่นี่ด้วยสแตติกเมธอดแม้ว่าจะเป็นเมธอดa.static_foo
เพียงส่งคืนฟังก์ชัน ole ที่ดีโดยไม่มีอาร์กิวเมนต์ที่ถูกผูกไว้ static_foo
คาดหวัง 1 ข้อโต้แย้งและ
a.static_foo
คาดว่า 1 ข้อโต้แย้งเช่นกัน
print(a.static_foo)
# <function static_foo at 0xb7d479cc>
และแน่นอนว่าสิ่งเดียวกันนี้เกิดขึ้นเมื่อคุณโทรหาstatic_foo
ชั้นเรียนA
แทน
print(A.static_foo)
# <function static_foo at 0xb7d479cc>
@staticmethod
อาจช่วยจัดระเบียบโค้ดของคุณโดยการแทนที่ subclasses หากไม่มีคุณก็จะมีฟังก์ชันหลากหลายที่ลอยอยู่ในโมดูลเนมสเปซ
@staticmethod
- คุณสามารถใช้มันเพื่อลบ cruft ฉันกำลังใช้ภาษาการเขียนโปรแกรมใน Python - ฟังก์ชั่นที่ห้องสมุดกำหนดใช้execute
วิธีการคงที่ซึ่งฟังก์ชั่นที่ผู้ใช้กำหนดต้องใช้อาร์กิวเมนต์เช่น (ร่างกายของฟังก์ชั่น) ผู้ตกแต่งนี้จะกำจัดคำเตือน "พารามิเตอร์ที่ไม่ได้ใช้ด้วยตนเอง" ในตัวตรวจสอบ PyCharm
StaticMethodเป็นวิธีการที่รู้อะไรเกี่ยวกับชั้นเรียนหรืออินสแตนซ์มันถูกเรียกว่าบน มันเพิ่งได้รับการขัดแย้งที่ถูกส่งผ่านไม่มีข้อโต้แย้งแรกโดยปริยาย โดยทั่วไปแล้วมันไม่มีประโยชน์ใน Python คุณสามารถใช้ฟังก์ชั่นโมดูลแทนการใช้สแตติก
classmethodบนมืออื่น ๆ ที่เป็นวิธีการที่ได้รับผ่านการเรียนมันถูกเรียกว่าบนหรือระดับของอินสแตนซ์ที่มันถูกเรียกเป็นอาร์กิวเมนต์แรก สิ่งนี้มีประโยชน์เมื่อคุณต้องการให้เมธอดเป็น factory สำหรับคลาส: เนื่องจากได้รับคลาสจริงซึ่งถูกเรียกใช้เป็นอาร์กิวเมนต์แรกคุณสามารถสร้างอินสแตนซ์ของคลาสที่ถูกต้องได้เสมอแม้ว่าจะเกี่ยวข้องกับคลาสย่อยก็ตาม สังเกตว่าอินสแตนซ์dict.fromkeys()
classmethod ส่งคืนอินสแตนซ์ของคลาสย่อยอย่างไรเมื่อถูกเรียกบนคลาสย่อย:
>>> class DictSubclass(dict):
... def __repr__(self):
... return "DictSubclass"
...
>>> dict.fromkeys("abc")
{'a': None, 'c': None, 'b': None}
>>> DictSubclass.fromkeys("abc")
DictSubclass
>>>
โดยทั่วไปแล้ว@classmethod
ทำให้วิธีการที่มีข้อโต้แย้งแรกคือชั้นมันถูกเรียกจาก (แทนที่จะเป็นอินสแตนซ์ชั้นเรียน) @staticmethod
ไม่มีข้อโต้แย้งโดยนัยใด ๆ
เอกสารหลามอย่างเป็นทางการ:
วิธีการเรียนได้รับชั้นเรียนเป็นอาร์กิวเมนต์แรกโดยปริยายเช่นเดียวกับวิธีการได้รับตัวอย่างเช่น ในการประกาศเมธอดคลาสให้ใช้สำนวนนี้:
class C: @classmethod def f(cls, arg1, arg2, ...): ...
@classmethod
รูปแบบเป็นฟังก์ชั่น มัณฑนา - ดูคำอธิบายของคำจำกัดความของฟังก์ชั่นในคำจำกัดความของฟังก์ชั่นสำหรับรายละเอียดมันสามารถเรียกได้ทั้งในชั้นเรียน (เช่น
C.f()
) หรือในอินสแตนซ์ (เช่นC().f()
) อินสแตนซ์ถูกละเว้นยกเว้นคลาสของมัน ถ้าวิธีการเรียนถูกเรียกสำหรับคลาสที่ได้รับวัตถุคลาสที่ได้รับจะถูกส่งผ่านเป็นอาร์กิวเมนต์แรกโดยนัยวิธีการเรียนแตกต่างจาก C ++ หรือวิธีการคงที่ Java หากคุณต้องการสิ่งเหล่านั้นโปรดดู
staticmethod()
ในส่วนนี้
วิธีการคงที่ไม่ได้รับการโต้แย้งครั้งแรกโดยปริยาย ในการประกาศเมธอดสแตติกให้ใช้สำนวนนี้:
class C: @staticmethod def f(arg1, arg2, ...): ...
@staticmethod
รูปแบบเป็นฟังก์ชั่น มัณฑนา - ดูคำอธิบายของคำจำกัดความของฟังก์ชั่นในคำจำกัดความของฟังก์ชั่นสำหรับรายละเอียดมันสามารถเรียกได้ทั้งในชั้นเรียน (เช่น
C.f()
) หรือในอินสแตนซ์ (เช่นC().f()
) อินสแตนซ์ถูกละเว้นยกเว้นคลาสของมันวิธีการคงที่ใน Python คล้ายกับที่พบใน Java หรือ C ++ สำหรับแนวคิดขั้นสูงเพิ่มเติมให้ดู
classmethod()
ในส่วนนี้
นี่เป็นบทความสั้น ๆ เกี่ยวกับคำถามนี้
ฟังก์ชัน @staticmethod ไม่มีอะไรมากไปกว่าฟังก์ชั่นที่กำหนดไว้ในชั้นเรียน มันเป็น callable โดยไม่ต้องยกระดับชั้นก่อน ความหมายของมันไม่เปลี่ยนรูปผ่านการสืบทอด
ฟังก์ชัน @classmethod ยังสามารถเรียกใช้ได้โดยไม่ต้องทำให้คลาสเป็นอินสแตนซ์ แต่คำจำกัดความของมันจะตามมาด้วยคลาสย่อยไม่ใช่คลาสพาเรนต์ผ่านการสืบทอด นั่นเป็นเพราะอาร์กิวเมนต์แรกสำหรับฟังก์ชัน @classmethod ต้องเป็น cls (คลาส) เสมอ
ในการตัดสินใจว่าจะใช้@staticmethodหรือ@classmethodคุณต้องดูวิธีการของคุณ หากวิธีการของคุณเข้าถึงตัวแปรอื่น ๆ / วิธีการในชั้นเรียนของคุณแล้วใช้ @classmethod ในทางกลับกันหากวิธีการของคุณไม่แตะต้องส่วนอื่น ๆ ของชั้นเรียนให้ใช้ @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
จะยังคงเป็นcls._counter
แม้ว่ารหัสจะถูกใส่ในชั้นเรียนที่แตกต่างกันหรือเปลี่ยนชื่อชั้นเรียน Apple._counter
เฉพาะสำหรับApple
ชั้นเรียน สำหรับคลาสอื่นหรือเมื่อเปลี่ยนชื่อคลาสคุณจะต้องเปลี่ยนคลาสที่อ้างอิง
@staticmethod แตกต่างจาก @classmethod ใน Python แตกต่างกันอย่างไร
คุณอาจเห็นรหัส Python เช่น pseudocode นี้ซึ่งแสดงให้เห็นถึงลายเซ็นต์ของวิธีการประเภทต่างๆและให้ docstring เพื่ออธิบายแต่ละรายการ:
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.
'''
a_normal_instance_method
ครั้งแรกที่ฉันจะอธิบาย สิ่งนี้เรียกว่า " วิธีการอินสแตนซ์ " อย่างแม่นยำ " เมื่อใช้วิธีการอินสแตนซ์จะใช้เป็นฟังก์ชันบางส่วน (ซึ่งตรงข้ามกับฟังก์ชั่นทั้งหมดที่กำหนดไว้สำหรับค่าทั้งหมดเมื่อดูในซอร์สโค้ด) นั่นคือเมื่อใช้อาร์กิวเมนต์แรกจะถูกกำหนดไว้ล่วงหน้าเป็นอินสแตนซ์ของ วัตถุที่มีคุณสมบัติที่กำหนดทั้งหมด มันมีอินสแตนซ์ของวัตถุที่ถูกผูกไว้กับมันและมันจะต้องถูกเรียกจากอินสแตนซ์ของวัตถุ โดยทั่วไปจะเข้าถึงคุณลักษณะต่างๆของอินสแตนซ์
ตัวอย่างเช่นนี่เป็นตัวอย่างของสตริง:
', '
ถ้าเราใช้เมธอดอินสแตนซ์join
บนสตริงนี้เพื่อเข้าร่วม iterable อื่นมันค่อนข้างชัดเจนว่าเป็นฟังก์ชั่นของอินสแตนซ์นอกเหนือจากการเป็นฟังก์ชั่นของรายการที่ทำซ้ำได้['a', 'b', 'c']
:
>>> ', '.join(['a', 'b', 'c'])
'a, b, c'
วิธีการอินสแตนซ์สามารถผูกผ่านการค้นหาประเพื่อใช้ในภายหลัง
ตัวอย่างเช่นstr.join
วิธีนี้ผูกกับ':'
อินสแตนซ์:
>>> join_with_colons = ':'.join
และต่อมาเราสามารถใช้สิ่งนี้เป็นฟังก์ชั่นที่มีอาร์กิวเมนต์แรกที่ถูกผูกไว้กับมัน ด้วยวิธีนี้มันทำงานเหมือนฟังก์ชั่นบางส่วนในอินสแตนซ์:
>>> join_with_colons('abcde')
'a:b:c:d:e'
>>> join_with_colons(['FF', 'FF', 'FF', 'FF', 'FF', 'FF'])
'FF:FF:FF:FF:FF:FF'
วิธีการคงที่ไม่ได้ใช้อินสแตนซ์เป็นอาร์กิวเมนต์
มันคล้ายกับฟังก์ชั่นระดับโมดูลมาก
อย่างไรก็ตามฟังก์ชั่นระดับโมดูลจะต้องอยู่ในโมดูลและนำเข้าเป็นพิเศษไปยังสถานที่อื่น ๆ ที่มีการใช้งาน
หากมีการแนบกับวัตถุก็จะติดตามวัตถุได้อย่างสะดวกผ่านการนำเข้าและการสืบทอดเช่นกัน
ตัวอย่างของวิธีการคงถูกstr.maketrans
ย้ายจากstring
โมดูลในหลาม 3. str.translate
มันทำให้ตารางการแปลที่เหมาะสมสำหรับการบริโภค มันดูค่อนข้างงี่เง่าเมื่อใช้จากอินสแตนซ์ของสตริงดังที่แสดงไว้ด้านล่าง แต่การนำเข้าฟังก์ชั่นจากstring
โมดูลนั้นค่อนข้างเงอะงะและเป็นเรื่องดีที่สามารถเรียกมันจากคลาสได้เช่นเดียวกับใน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}
ใน python 2 คุณจะต้องนำเข้าฟังก์ชั่นนี้จากโมดูลสตริงที่มีประโยชน์น้อยลง:
>>> import string
>>> 'ABCDEFG'.translate(string.maketrans('ABC', 'abc'))
'abcDEFG'
วิธีการเรียนจะคล้ายกับวิธีการอินสแตนซ์ในการที่มันจะโต้แย้งแรกโดยปริยาย แต่แทนที่จะใช้ตัวอย่างมันจะใช้เวลาเรียน บ่อยครั้งที่สิ่งเหล่านี้ถูกใช้เป็นตัวสร้างทางเลือกสำหรับการใช้ความหมายที่ดีขึ้นและมันจะสนับสนุนการสืบทอด
ตัวอย่างที่เป็นที่ยอมรับมากที่สุดของ classmethod builtin dict.fromkeys
คือ มันถูกใช้เป็นตัวสร้างทางเลือกของ dict (เหมาะสำหรับเมื่อคุณรู้ว่าคีย์ของคุณคืออะไรและต้องการค่าเริ่มต้นสำหรับพวกเขา)
>>> dict.fromkeys(['a', 'b', 'c'])
{'c': None, 'b': None, 'a': None}
เมื่อเรา subclass dict เราสามารถใช้นวกรรมิกเดียวกันซึ่งสร้างตัวอย่างของ subclass
>>> 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'>
ดูรหัสต้นฉบับหมีแพนด้าสำหรับตัวอย่างอื่น ๆ ที่คล้ายกันของการก่อสร้างทางเลือกและดูเอกสารอย่างเป็นทางการในหลามและclassmethod
staticmethod
ฉันเริ่มเรียนรู้การเขียนโปรแกรมภาษาด้วย C ++ จากนั้น Java และ Python ดังนั้นคำถามนี้รบกวนฉันมากเช่นกันจนกระทั่งฉันเข้าใจการใช้งานที่เรียบง่ายของแต่ละคน
วิธีการเรียน:งูหลามซึ่งแตกต่างจาก Java และ C ++ ไม่มีคอนสตรัคโหลดมากเกินไป classmethod
และเพื่อที่จะบรรลุเป้าหมายนี้คุณสามารถใช้ ตัวอย่างต่อไปนี้จะอธิบายสิ่งนี้
ลองพิจารณาเรามีPerson
ระดับซึ่งจะใช้เวลาสองมีปากเสียงfirst_name
และและสร้างตัวอย่างของlast_name
Person
class Person(object):
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
ตอนนี้ถ้าความต้องการมาที่คุณต้องการสร้างคลาสโดยใช้ชื่อเดียวเท่านั้นfirst_name
คุณจะไม่สามารถทำสิ่งนี้ใน Python
สิ่งนี้จะทำให้คุณมีข้อผิดพลาดเมื่อคุณพยายามสร้างวัตถุ (อินสแตนซ์)
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
อย่างไรก็ตามคุณสามารถบรรลุสิ่งเดียวกันโดยใช้@classmethod
ตามที่ระบุไว้ด้านล่าง
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, "")
วิธีการคงที่:นี่ค่อนข้างง่ายไม่ผูกพันกับอินสแตนซ์หรือคลาสและคุณสามารถเรียกใช้ชื่อคลาสได้
ดังนั้นสมมติว่าในตัวอย่างข้างต้นคุณต้องมีการตรวจสอบที่first_name
ไม่ควรเกิน 20 ตัวอักษรคุณสามารถทำได้
@staticmethod
def validate_name(name):
return len(name) <= 20
และคุณสามารถโทรโดยใช้ class name
Person.validate_name("Gaurang Shah")
def __init__(self, first_name, last_name="")
แทนวิธีการget_person
เรียน และผลลัพธ์จะเหมือนกันทุกประการในกรณีนี้
ฉันคิดว่าคำถามที่ดีกว่าคือ "เมื่อใดที่คุณจะใช้ @classmethod vs @staticmethod"
@classmethod ช่วยให้คุณสามารถเข้าถึงสมาชิกส่วนตัวที่เกี่ยวข้องกับคำจำกัดความของคลาสได้อย่างง่ายดาย นี่เป็นวิธีที่ยอดเยี่ยมในการทำซิงเกิลตันหรือคลาสของโรงงานที่ควบคุมจำนวนอินสแตนซ์ของวัตถุที่สร้างขึ้น
@staticmethod ให้ประสิทธิภาพที่เพิ่มขึ้นเล็กน้อย แต่ฉันยังไม่เห็นวิธีการใช้งานที่มีประสิทธิผลของคลาสแบบคงที่ภายในคลาสที่ไม่สามารถทำได้ในรูปแบบฟังก์ชันสแตนด์อโลนนอกคลาส
@decorators ถูกเพิ่มใน python 2.4 หากคุณใช้ python <2.4 คุณสามารถใช้ฟังก์ชัน classmethod () และ staticmethod ()
ตัวอย่างเช่นหากคุณต้องการสร้างวิธีการจากโรงงาน (ฟังก์ชั่นที่ส่งคืนอินสแตนซ์ของการใช้คลาสที่แตกต่างกันขึ้นอยู่กับอาร์กิวเมนต์ที่ได้รับ) คุณสามารถทำสิ่งต่อไปนี้:
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)
นอกจากนี้ให้สังเกตว่านี่เป็นตัวอย่างที่ดีสำหรับการใช้ classmethod และ static method, วิธีการ static เป็นของคลาสอย่างชัดเจนเนื่องจากมันใช้คลาส Cluster ภายใน เมธอด class ต้องการเพียงข้อมูลเกี่ยวกับคลาสและไม่มีอินสแตนซ์ของวัตถุ
ข้อดีอีกอย่างของการสร้าง_is_cluster_for
เมธอด classmethod ก็คือเพื่อให้คลาสย่อยสามารถตัดสินใจที่จะเปลี่ยนการใช้งานอาจเป็นเพราะมันเป็นเรื่องทั่วไปและสามารถจัดการกับคลัสเตอร์มากกว่าหนึ่งประเภทดังนั้นเพียงแค่ตรวจสอบชื่อของคลาสที่จะไม่เพียงพอ
วิธีการคงที่:
ประโยชน์ของวิธีการคงที่:
สะดวกกว่าในการนำเข้าเมื่อเทียบกับฟังก์ชั่นระดับโมดูลเนื่องจากแต่ละวิธีไม่จำเป็นต้องนำเข้าเป็นพิเศษ
@staticmethod
def some_static_method(*args, **kwds):
pass
วิธีการเรียน:
เหล่านี้ถูกสร้างขึ้นด้วยฟังก์ชั่นชั้นเรียนวิธีการในการสร้าง
@classmethod
def some_class_method(cls, *args, **kwds):
pass
@staticmethod
เพียงแค่ปิดการใช้งานฟังก์ชั่นเริ่มต้นเป็นอธิบายวิธีการ classmethod ล้อมรอบฟังก์ชันของคุณใน container callable ที่ส่งการอ้างอิงไปยังคลาสที่เป็นเจ้าของว่าเป็นอาร์กิวเมนต์แรก:
>>> 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'>>
ตามจริงแล้วclassmethod
มีโอเวอร์เฮดรันไทม์ แต่ทำให้สามารถเข้าถึงคลาสที่เป็นเจ้าของได้ หรือฉันแนะนำให้ใช้ metaclass และวางวิธีการเรียนใน metaclass ที่:
>>> class CMeta(type):
... def foo(cls):
... print cls
...
>>> class C(object):
... __metaclass__ = CMeta
...
>>> C.foo()
<class '__main__.C'>
c = C(); c.foo()
ยก AttributeError type(c).foo()
คุณจะต้องทำ นี่อาจเป็นคุณสมบัติ - ฉันคิดไม่ออกว่าทำไมคุณถึงต้องการ
คำแนะนำที่ชัดเจนเกี่ยวกับวิธีการใช้วิธีการคงที่คลาสหรือนามธรรมใน Pythonเป็นลิงค์ที่ดีสำหรับหัวข้อนี้และสรุปได้ดังต่อไปนี้
@staticmethod
ฟังก์ชั่นคืออะไรมากกว่าฟังก์ชั่นที่กำหนดไว้ในชั้นเรียน มันเป็น callable โดยไม่ต้องยกระดับชั้นก่อน ความหมายของมันไม่เปลี่ยนรูปผ่านการสืบทอด
@classmethod
ฟังก์ชั่นยัง callable โดยไม่ต้องยกระดับชั้นเรียน แต่คำจำกัดความของมันตามชั้นย่อยไม่ใช่ชั้นผู้ปกครองผ่านการสืบทอดสามารถถูกแทนที่โดย subclass นั่นเป็นเพราะอาร์กิวเมนต์แรกสำหรับ@classmethod
ฟังก์ชั่นจะต้องเป็นcls (คลาส) เสมอ
เฉพาะอาร์กิวเมนต์แรกเท่านั้นที่แตกต่าง :
รายละเอียดเพิ่มเติม ...
เมื่อมีการเรียกเมธอดของวัตถุมันจะได้รับอาร์กิวเมนต์พิเศษself
เป็นอาร์กิวเมนต์แรกโดยอัตโนมัติ นั่นคือวิธีการ
def f(self, x, y)
ต้องถูกเรียกด้วย 2 อาร์กิวเมนต์ self
จะถูกส่งโดยอัตโนมัติและเป็นวัตถุเองวัตถุเอง
เมื่อวิธีการตกแต่ง
@classmethod
def f(cls, x, y)
อาร์กิวเมนต์ที่ระบุโดยอัตโนมัติไม่ได้ self
แต่เป็นคลาสของ self
ระดับของ
เมื่อวิธีการตกแต่ง
@staticmethod
def f(x, y)
วิธีการไม่ได้รับอาร์กิวเมนต์อัตโนมัติใด ๆ เลย มันจะได้รับเฉพาะพารามิเตอร์ที่มันถูกเรียกด้วย
classmethod
ส่วนใหญ่จะใช้สำหรับการก่อสร้างทางเลือก staticmethod
ไม่ใช้สถานะของวัตถุ มันอาจเป็นฟังก์ชั่นภายนอกกับคลาส มันใส่ไว้ในชั้นเรียนสำหรับการจัดกลุ่มฟังก์ชั่นที่มีฟังก์ชั่นที่คล้ายกัน (ตัวอย่างเช่นMath
วิธีการเรียนแบบคงที่ของ 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)
ให้ฉันบอกความคล้ายคลึงกันระหว่างวิธีที่ตกแต่งด้วย @classmethod vs @staticmethod ก่อน
ความคล้ายคลึงกัน:ทั้งคู่สามารถถูกเรียกใช้บนคลาสเองแทนที่จะเป็นเพียงตัวอย่างของคลาส ดังนั้นทั้งสองของพวกเขาในความรู้สึกที่มีวิธีการเรียนของ
ความแตกต่าง:วิธีการเรียนจะได้รับชั้นเรียนเป็นอาร์กิวเมนต์แรกในขณะที่วิธีการแบบคงที่ไม่ได้
ดังนั้นวิธีการแบบคงที่ในความรู้สึกไม่ผูกพันกับระดับตัวเองและเป็นเพียงการแขวนในนั้นเพียงเพราะมันอาจมีฟังก์ชั่นที่เกี่ยวข้อง
>>> 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',)
ข้อพิจารณาอีกประการหนึ่งเกี่ยวกับ staticmethod vs classmethod เกิดขึ้นกับการสืบทอด สมมติว่าคุณมีคลาสต่อไปนี้:
class Foo(object):
@staticmethod
def bar():
return "In Foo"
และคุณต้องการแทนที่bar()
ในคลาสย่อย:
class Foo2(Foo):
@staticmethod
def bar():
return "In Foo2"
ใช้งานได้ แต่โปรดทราบว่าขณะนี้การbar()
ใช้งานในระดับย่อย ( Foo2
) ไม่สามารถใช้ประโยชน์จากสิ่งที่เฉพาะเจาะจงกับชั้นเรียนนั้นอีกต่อไป ตัวอย่างเช่นสมมติว่าFoo2
มีวิธีที่เรียกmagic()
ว่าคุณต้องการใช้ในการFoo2
ดำเนินการ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"
วิธีแก้ปัญหาที่นี่คือการโทรFoo2.magic()
เข้าbar()
แต่แล้วคุณจะทำซ้ำตัวเอง (ถ้าชื่อของFoo2
การเปลี่ยนแปลงคุณจะต้องจำไว้ว่าต้องอัปเดตbar()
วิธีการนั้น)
สำหรับฉันนี่เป็นการละเมิดหลักการเปิด / ปิดเล็กน้อยเนื่องจากการตัดสินใจFoo
ส่งผลกระทบต่อความสามารถของคุณในการสร้างรหัสทั่วไปในคลาสที่ได้รับ (เช่นเปิดน้อยกว่าการขยาย) ถ้าbar()
เป็นclassmethod
เราต้องการจะปรับ:
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()
ให้: In Foo2 MAGIC
ฉันจะพยายามอธิบายความแตกต่างพื้นฐานโดยใช้ตัวอย่าง
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 - เราสามารถเรียกใช้สแตติกและคลาสเมธอดได้โดยตรงโดยไม่ต้องกำหนดค่าเริ่มต้น
# A.run_self() # wrong
A.run_static()
A.run_class()
2- วิธีการคงที่ไม่สามารถเรียกวิธีการด้วยตนเอง แต่สามารถเรียกวิธีคงที่และ classmethod อื่น ๆ
3- วิธีสแตติกเป็นของคลาสและจะไม่ใช้วัตถุเลย
4- วิธีการเรียนไม่ได้ผูกไว้กับวัตถุ แต่ไปเรียน
@classmethod: สามารถใช้เพื่อสร้างการเข้าถึงทั่วโลกที่ใช้ร่วมกันไปยังอินสแตนซ์ทั้งหมดที่สร้างของคลาสนั้น ... เช่นการอัปเดตระเบียนโดยผู้ใช้หลายคน .... ฉันพบว่ามันใช้ ful เมื่อสร้าง singletons เช่นกัน .. : )
วิธี @ คงที่: มีอะไรจะทำอย่างไรกับชั้นเรียนหรืออินสแตนซ์ที่เกี่ยวข้องกับ ... แต่สำหรับการอ่านสามารถใช้วิธีการคงที่
คุณอาจต้องการพิจารณาความแตกต่างระหว่าง:
Class A:
def foo(): # no self parameter, no decorator
pass
และ
Class B:
@staticmethod
def foo(): # no self parameter
pass
สิ่งนี้มีการเปลี่ยนแปลงระหว่าง python2 และ python3:
python2:
>>> A.foo()
TypeError
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()
python3:
>>> A.foo()
>>> A().foo()
TypeError
>>> B.foo()
>>> B().foo()
ดังนั้นการใช้ @staticmethod
เมธอดที่เรียกได้โดยตรงจากคลาสจึงกลายเป็นทางเลือกใน python3 ถ้าคุณต้องการเรียกพวกเขาจากทั้งคลาสและอินสแตนซ์คุณยังคงต้องใช้@staticmethod
มัณฑนากร
กรณีอื่น ๆ ได้รับการครอบคลุมอย่างดีจากคำตอบ unutbus
วิธีการเรียนได้รับชั้นเรียนเป็นอาร์กิวเมนต์แรกโดยปริยายเช่นเดียวกับวิธีการได้รับตัวอย่างเช่น มันเป็นวิธีการที่ถูกผูกไว้กับชั้นเรียนและไม่ได้เป็นวัตถุของชั้นเรียน แต่ก็มีการเข้าถึงสถานะของชั้นเรียนเพราะมันใช้พารามิเตอร์ชั้นเรียนที่ชี้ไปที่ชั้นเรียนและไม่อินสแตนซ์วัตถุ มันสามารถแก้ไขสถานะคลาสที่จะใช้กับอินสแตนซ์ทั้งหมดของคลาส ตัวอย่างเช่นมันสามารถปรับเปลี่ยนตัวแปรคลาสที่จะใช้กับทุกกรณี
ในทางตรงกันข้ามวิธีการคงที่ไม่ได้รับการโต้แย้งครั้งแรกโดยนัยเมื่อเทียบกับวิธีการเรียนหรือวิธีการเช่น และไม่สามารถเข้าถึงหรือแก้ไขสถานะคลาสได้ มันเป็นของคลาสเพราะจากมุมมองการออกแบบที่เป็นวิธีที่ถูกต้อง แต่ในแง่ของฟังก์ชั่นไม่ได้ถูกผูกไว้ที่ runtime กับคลาส
เป็นแนวทางให้ใช้วิธีการคงที่เป็นสาธารณูปโภคใช้วิธีการเรียนเช่นโรงงาน หรืออาจจะกำหนดซิงเกิล และใช้วิธีการอินสแตนซ์เพื่อจำลองสถานะและพฤติกรรมของอินสแตนซ์
หวังว่าฉันจะชัดเจน!
ผลงานของฉันแสดงให้เห็นถึงความแตกต่างในหมู่@classmethod
, และเช่นวิธีการรวมถึงวิธีตัวอย่างทางอ้อมสามารถเรียก@staticmethod
@staticmethod
แต่แทนที่จะเรียกทางอ้อม@staticmethod
จากอินสแตนซ์การทำให้เป็นส่วนตัวอาจเป็น "pythonic" มากกว่า การรับบางสิ่งจากวิธีส่วนตัวไม่ได้แสดงไว้ที่นี่ แต่โดยพื้นฐานแล้วเป็นแนวคิดเดียวกัน
#!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)
'''
"""
วิธีการเรียนตามชื่อแนะนำจะใช้ในการเปลี่ยนแปลงชั้นเรียนและไม่ใช่วัตถุ ในการเปลี่ยนแปลงคลาสพวกเขาจะปรับเปลี่ยนคลาสคลาส (ไม่ใช่แอตทริบิวต์ของออบเจ็กต์) เนื่องจากเป็นวิธีที่คุณอัพเดตคลาส นี่คือเหตุผลที่วิธีการเรียนใช้คลาส (แสดงโดยอัตภาพโดย 'cls') เป็นอาร์กิวเมนต์แรก
class A(object):
m=54
@classmethod
def class_method(cls):
print "m is %d" % cls.m
วิธีการคงที่ในมืออื่น ๆ ที่ใช้ในการปฏิบัติหน้าที่ที่ไม่ผูกพันกับชั้นเรียนคือพวกเขาจะไม่อ่านหรือเขียนตัวแปรระดับ ดังนั้นวิธีการคงที่ไม่ได้เรียนเป็นอาร์กิวเมนต์ พวกเขาจะใช้เพื่อให้ชั้นเรียนสามารถปฏิบัติหน้าที่ที่ไม่เกี่ยวข้องโดยตรงกับวัตถุประสงค์ของการเรียน
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."
วิเคราะห์ @staticmethod อย่างแท้จริงให้ข้อมูลเชิงลึกที่แตกต่างกัน
วิธีการปกติของชั้นเรียนเป็นวิธีแบบไดนามิกโดยนัยซึ่งใช้อินสแตนซ์เป็นอาร์กิวเมนต์แรก
ในทางตรงกันข้าม StaticMethod ไม่ได้ใช้อินสแตนซ์เป็นอาร์กิวเมนต์แรกดังนั้นจะเรียกว่า'คงที่'
สแตติกเมธอดนั้นเป็นฟังก์ชั่นปกติเช่นเดียวกันกับที่อยู่นอกคำจำกัดความของคลาส
มันถูกจัดกลุ่มเข้าชั้นเรียนอย่างใกล้ชิดเพื่อที่จะเข้าใกล้มันมากขึ้นหรือคุณอาจเลื่อนไปรอบ ๆ เพื่อหามัน
ฉันคิดว่าให้รุ่น Python อย่างหมดจดstaticmethod
และclassmethod
จะช่วยให้เข้าใจความแตกต่างระหว่างพวกเขาในระดับภาษา
ทั้งคู่เป็นตัวอธิบายที่ไม่ใช่ข้อมูล (มันจะง่ายต่อการเข้าใจพวกเขาหากคุณคุ้นเคยกับตัวอธิบายก่อน)
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 ไม่มีสิทธิ์เข้าถึง attibutes ของวัตถุ, class หรือ parent class ในลำดับชั้นการสืบทอด มันสามารถเรียกได้ที่ชั้นเรียนโดยตรง (โดยไม่ต้องสร้างวัตถุ)
classmethod ไม่มีสิทธิ์เข้าถึงคุณลักษณะของวัตถุ อย่างไรก็ตามสามารถเข้าถึงคุณลักษณะของคลาสและคลาสแม่ในลำดับชั้นการสืบทอด มันสามารถเรียกได้ที่ชั้นเรียนโดยตรง (โดยไม่ต้องสร้างวัตถุ) ถ้าเรียกที่วัตถุมันก็เหมือนกับวิธีปกติที่ไม่สามารถเข้าถึงself.<attribute(s)>
และเข้าถึงได้self.__class__.<attribute(s)>
เท่านั้น
คิดว่าเรามีคลาสด้วยb=2
เราจะสร้างวัตถุและตั้งค่าใหม่ให้b=4
เป็นมัน Staticmethod ไม่สามารถเข้าถึงอะไรจากก่อนหน้านี้ Classmethod สามารถเข้าถึงเพียงผ่าน.b==2
cls.b
วิธีการปกติสามารถเข้าถึงทั้งสอง.b==4
ผ่านself.b
และผ่าน.b==2
self.__class__.b
เราสามารถทำตามสไตล์จูบ (ให้มันง่ายโง่): อย่าใช้ staticmethods และ classmethods ไม่ได้ใช้เรียนได้โดยไม่ต้อง instantiating self.attribute(s)
พวกเขาเพียงแอตทริบิวต์วัตถุของการเข้าถึง มีภาษาที่ใช้ OOP ด้วยวิธีนี้และฉันคิดว่าไม่ใช่ความคิดที่ไม่ดี :)
วิธีที่เหมือนกันอย่างรวดเร็วของการแฮ็กขึ้นใน iPython เผยให้เห็นว่า@staticmethod
ได้รับประสิทธิภาพที่เพิ่มขึ้นเล็กน้อย (เป็นนาโนวินาที) แต่อย่างอื่นดูเหมือนว่าจะไม่สามารถใช้งานได้ นอกจากนี้การเพิ่มประสิทธิภาพใด ๆ อาจจะถูกลบออกโดยงานเพิ่มเติมของการประมวลผลวิธีการstaticmethod()
ในระหว่างการรวบรวม (ซึ่งเกิดขึ้นก่อนการเรียกใช้รหัสใด ๆ เมื่อคุณเรียกใช้สคริปต์)
เพื่อประโยชน์ในการอ่านโค้ดฉันจะหลีกเลี่ยง@staticmethod
เว้นแต่ว่าวิธีการของคุณจะถูกใช้สำหรับการทำงานที่มีจำนวนนาโนวินาที