ความแตกต่างระหว่างวิธีการคงที่และวิธีการเรียน


3579

อะไรคือความแตกต่างระหว่างฟังก์ชั่นที่ตกแต่งด้วย@staticmethodและฟังก์ชั่นที่ตกแต่งด้วย@classmethod?


11
บางครั้งวิธีสแตติกจะดีกว่าเนื่องจากฟังก์ชันระดับโมดูลในไพ ธ อนเพื่อความสะอาด ด้วยฟังก์ชั่นโมดูลมันจะง่ายกว่าที่จะนำเข้าเฉพาะฟังก์ชั่นที่คุณต้องการและป้องกันไม่จำเป็น "" ไวยากรณ์ (ฉันกำลังดูคุณ Objective-C) วิธีการเรียนมีประโยชน์มากขึ้นเนื่องจากสามารถใช้ร่วมกับ polymorphism เพื่อสร้างฟังก์ชั่น "รูปแบบโรงงาน" นี่เป็นเพราะวิธีการเรียนได้รับชั้นเรียนเป็นพารามิเตอร์โดยนัย
FistOfFury

27
tl; dr >> เมื่อเปรียบเทียบกับวิธีการทั่วไปวิธีการแบบคงที่และวิธีการเรียนสามารถเข้าถึงได้โดยใช้ชั้นเรียน แต่แตกต่างจากวิธีการเรียนวิธีการแบบคงที่จะไม่เปลี่ยนรูปผ่านการสืบทอด
imsrgadich

4
พูดคุยที่เกี่ยวข้องโดย Raymond Hettinger ในหัวข้อ: youtube.com/watch?v=HTLu2DFOdTg
moooeeeep

youtube.com/watch?v=HTLu2DFOdTg&feature=youtu.be&t=2689 ที่แม่นยำยิ่งขึ้นคุณต้องการเพียง classmethod สำหรับผู้สร้างทางเลือก มิฉะนั้นคุณสามารถใช้ staticmethod และเข้าถึงแอตทริบิวต์ class ใด ๆ (ผ่าน. / dot) โดย "CLASSNAME" ที่ให้ข้อมูลมากกว่านี้แทน cls เหมือนใน classmethod
Robert Nowak

คำตอบ:


3137

บางทีโค้ดตัวอย่างเล็กน้อยจะช่วย: สังเกตเห็นความแตกต่างในลายเซ็นการโทรของ 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ค่อนข้างชั้นถูกผูกไว้กับAclass_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>

182
ฉันไม่เข้าใจสิ่งที่จับสำหรับการใช้วิธีการแบบคงที่ เราสามารถใช้ฟังก์ชั่นนอกห้องเรียนที่เรียบง่าย
Alcott

372
@Alcott: คุณอาจต้องการย้ายฟังก์ชั่นเข้าชั้นเรียนเพราะมันเป็นเหตุผลในชั้นเรียน ในซอร์สโค้ดของ Python (เช่นมัลติโพรเซสซิงเทอร์เทิลแพคเกจ) มันถูกใช้เพื่อ "ซ่อน" ฟังก์ชั่น "เดี่ยว" ขีดล่าง "ส่วนตัว" จากเนมสเปซโมดูล แม้ว่าการใช้งานจะมีความเข้มข้นสูงในโมดูลเพียงไม่กี่ - บางทีบ่งชี้ว่ามันเป็นสิ่งที่โวหารส่วนใหญ่ แม้ว่าฉันจะไม่พบตัวอย่างใด ๆ เกี่ยวกับสิ่งนี้ แต่@staticmethodอาจช่วยจัดระเบียบโค้ดของคุณโดยการแทนที่ subclasses หากไม่มีคุณก็จะมีฟังก์ชันหลากหลายที่ลอยอยู่ในโมดูลเนมสเปซ
unutbu

15
... พร้อมกับคำอธิบายเกี่ยวกับสถานที่และสาเหตุที่ใช้อินสแตนซ์คลาสหรือสแตติกวิธีการอย่างใดอย่างหนึ่ง คุณไม่ได้พูดอะไรเกี่ยวกับมันสักคำ แต่ OP ไม่ได้ถามอะไรเลย
MestreLion

106
@Alcott: ตามที่ unutbu กล่าวว่าวิธีการแบบคงที่เป็นคุณลักษณะขององค์กร / สไตลิส บางครั้งโมดูลมีคลาสจำนวนมากและฟังก์ชั่นผู้ช่วยบางอย่างเชื่อมโยงกับคลาสที่กำหนดและไม่ใช่คลาสอื่นดังนั้นจึงไม่เหมาะสมที่จะ "ทำให้เกิดมลพิษ" โมดูลที่มี "ฟังก์ชั่นอิสระ" มากมายและควรใช้สแตติก วิธีการกว่าพึ่งพาสไตล์ที่ไม่ดีของการผสมคลาสและฟังก์ชั่น defs ร่วมกันในรหัสเพียงเพื่อแสดงว่าพวกเขาเป็น "ที่เกี่ยวข้อง"
MestreLion

4
อีกหนึ่งการใช้สำหรับ@staticmethod- คุณสามารถใช้มันเพื่อลบ cruft ฉันกำลังใช้ภาษาการเขียนโปรแกรมใน Python - ฟังก์ชั่นที่ห้องสมุดกำหนดใช้executeวิธีการคงที่ซึ่งฟังก์ชั่นที่ผู้ใช้กำหนดต้องใช้อาร์กิวเมนต์เช่น (ร่างกายของฟังก์ชั่น) ผู้ตกแต่งนี้จะกำจัดคำเตือน "พารามิเตอร์ที่ไม่ได้ใช้ด้วยตนเอง" ในตัวตรวจสอบ PyCharm
tehwalrus

798

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

714
สแตติกเมธอดไม่ไร้ประโยชน์ - เป็นวิธีการใส่ฟังก์ชั่นเข้าคลาส (เพราะมันเป็นเหตุผลอย่างมีเหตุผล) ในขณะที่ระบุว่ามันไม่ต้องการการเข้าถึงคลาส
Tony Meyer

136
ดังนั้นเพียง 'โดยทั่วไป' ไร้ประโยชน์ องค์กรดังกล่าวรวมถึงการฉีดพึ่งพาเป็นวิธีการที่ถูกต้องของการใช้ staticmethods แต่เนื่องจากโมดูลที่ไม่ได้เรียนใน Java เป็นองค์ประกอบพื้นฐานของการจัดระเบียบรหัสใน Python การใช้งานและประโยชน์ของพวกเขานั้นหายาก
โธมัสวูสเตอร์

40
มีเหตุผลเกี่ยวกับการกำหนดวิธีการภายในชั้นเรียนเมื่อมีอะไรจะทำอย่างไรกับทั้งชั้นเรียนหรือกรณี?
Ben James

106
บางทีเพื่อเห็นแก่มรดก วิธีการแบบคงที่สามารถสืบทอดและแทนที่เช่นเดียวกับวิธีการตัวอย่างและวิธีการเรียนและการค้นหาการทำงานตามที่คาดไว้ (ไม่เหมือนใน Java) วิธีการแบบสแตติกไม่ได้รับการแก้ไขแบบสแตติกจริง ๆ ไม่ว่าจะเรียกในคลาสหรืออินสแตนซ์ดังนั้นความแตกต่างเพียงอย่างเดียวระหว่างคลาสและสแตติกเมธอดคืออาร์กิวเมนต์แรกโดยนัย
haridsv

80
พวกเขายังสร้างเนมสเปซที่สะอาดขึ้นและทำให้ง่ายต่อการเข้าใจฟังก์ชั่นที่เกี่ยวข้องกับคลาส
Imbrondir

149

โดยทั่วไปแล้ว@classmethodทำให้วิธีการที่มีข้อโต้แย้งแรกคือชั้นมันถูกเรียกจาก (แทนที่จะเป็นอินสแตนซ์ชั้นเรียน) @staticmethodไม่มีข้อโต้แย้งโดยนัยใด ๆ


103

เอกสารหลามอย่างเป็นทางการ:

@classmethod

วิธีการเรียนได้รับชั้นเรียนเป็นอาร์กิวเมนต์แรกโดยปริยายเช่นเดียวกับวิธีการได้รับตัวอย่างเช่น ในการประกาศเมธอดคลาสให้ใช้สำนวนนี้:

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

@classmethodรูปแบบเป็นฟังก์ชั่น มัณฑนา - ดูคำอธิบายของคำจำกัดความของฟังก์ชั่นในคำจำกัดความของฟังก์ชั่นสำหรับรายละเอียด

มันสามารถเรียกได้ทั้งในชั้นเรียน (เช่นC.f()) หรือในอินสแตนซ์ (เช่นC().f()) อินสแตนซ์ถูกละเว้นยกเว้นคลาสของมัน ถ้าวิธีการเรียนถูกเรียกสำหรับคลาสที่ได้รับวัตถุคลาสที่ได้รับจะถูกส่งผ่านเป็นอาร์กิวเมนต์แรกโดยนัย

วิธีการเรียนแตกต่างจาก C ++ หรือวิธีการคงที่ Java หากคุณต้องการสิ่งเหล่านั้นโปรดดูstaticmethod()ในส่วนนี้

@staticmethod

วิธีการคงที่ไม่ได้รับการโต้แย้งครั้งแรกโดยปริยาย ในการประกาศเมธอดสแตติกให้ใช้สำนวนนี้:

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

@staticmethodรูปแบบเป็นฟังก์ชั่น มัณฑนา - ดูคำอธิบายของคำจำกัดความของฟังก์ชั่นในคำจำกัดความของฟังก์ชั่นสำหรับรายละเอียด

มันสามารถเรียกได้ทั้งในชั้นเรียน (เช่นC.f()) หรือในอินสแตนซ์ (เช่นC().f()) อินสแตนซ์ถูกละเว้นยกเว้นคลาสของมัน

วิธีการคงที่ใน Python คล้ายกับที่พบใน Java หรือ C ++ สำหรับแนวคิดขั้นสูงเพิ่มเติมให้ดู classmethod()ในส่วนนี้


ไม่มีข้อผิดพลาดในเอกสารใช่หรือไม่ ไม่ควรอยู่ที่ staticmethod: "อินสแตนซ์และคลาสนั้นถูกละเว้นทั้งคู่" แทนที่จะเป็น "อินสแตนซ์นั้นถูกละเว้นยกเว้นคลาสของมัน"?
mirek

มันอาจเป็นข้อผิดพลาดที่ถูกตัดและวาง แต่การพูดอย่างเคร่งครัดคุณไม่สามารถเรียกวิธีการในชั้นเรียนได้ถ้าคุณไม่สนใจชั้นเรียน
Aaron Bentley

76

นี่เป็นบทความสั้น ๆ เกี่ยวกับคำถามนี้

ฟังก์ชัน @staticmethod ไม่มีอะไรมากไปกว่าฟังก์ชั่นที่กำหนดไว้ในชั้นเรียน มันเป็น callable โดยไม่ต้องยกระดับชั้นก่อน ความหมายของมันไม่เปลี่ยนรูปผ่านการสืบทอด

ฟังก์ชัน @classmethod ยังสามารถเรียกใช้ได้โดยไม่ต้องทำให้คลาสเป็นอินสแตนซ์ แต่คำจำกัดความของมันจะตามมาด้วยคลาสย่อยไม่ใช่คลาสพาเรนต์ผ่านการสืบทอด นั่นเป็นเพราะอาร์กิวเมนต์แรกสำหรับฟังก์ชัน @classmethod ต้องเป็น cls (คลาส) เสมอ


1
นั่นหมายความว่าโดยการใช้ staticmethod ฉันมักจะผูกกับคลาสผู้ปกครองและกับ classmethod ฉันผูกพันคลาสที่ฉันประกาศ classmethod ใน (ในกรณีนี้เป็นคลาสย่อย)
Mohan Gulati

7
ไม่ได้โดยใช้วิธีการแบบคงที่คุณจะไม่ผูกพันเลย ไม่มีพารามิเตอร์แรกโดยนัย โดยการใช้ classmethod คุณจะได้รับพารามิเตอร์โดยปริยายเป็นคลาสที่คุณเรียกว่าเมธอด on (ถ้าคุณเรียกมันโดยตรงบนคลาส) หรือคลาสของอินสแตนซ์ที่คุณเรียกว่าเมธอด on (ถ้าคุณเรียกมันบนอินสแตนซ์)
Matt Anderson

6
อาจจะขยายตัวเล็กน้อยเพื่อแสดงให้เห็นว่าโดยมีชั้นเรียนเป็นอาร์กิวเมนต์แรกวิธีการเรียนมีการเข้าถึงโดยตรงแอตทริบิวต์ระดับอื่น ๆ และวิธีการในขณะที่วิธีการคงทำไม่ได้ (พวกเขาจะต้อง hardcode MyClass.attr สำหรับที่)
MestreLion

"คำจำกัดความของมันไม่เปลี่ยนรูปผ่านการสืบทอด" ไม่สมเหตุสมผลใน Python คุณสามารถแทนที่เมธอดสแตติกได้ดี
cz

68

ในการตัดสินใจว่าจะใช้@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

ข้อดีของ classmethod และ cls._counter กับ staticmethod และ Apple._counter คืออะไร
Robert Nowak

1
cls._counterจะยังคงเป็นcls._counterแม้ว่ารหัสจะถูกใส่ในชั้นเรียนที่แตกต่างกันหรือเปลี่ยนชื่อชั้นเรียน Apple._counterเฉพาะสำหรับAppleชั้นเรียน สำหรับคลาสอื่นหรือเมื่อเปลี่ยนชื่อคลาสคุณจะต้องเปลี่ยนคลาสที่อ้างอิง
kiamlaluno

53

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

ดูรหัสต้นฉบับหมีแพนด้าสำหรับตัวอย่างอื่น ๆ ที่คล้ายกันของการก่อสร้างทางเลือกและดูเอกสารอย่างเป็นทางการในหลามและclassmethodstaticmethod


43

ฉันเริ่มเรียนรู้การเขียนโปรแกรมภาษาด้วย C ++ จากนั้น Java และ Python ดังนั้นคำถามนี้รบกวนฉันมากเช่นกันจนกระทั่งฉันเข้าใจการใช้งานที่เรียบง่ายของแต่ละคน

วิธีการเรียน:งูหลามซึ่งแตกต่างจาก Java และ C ++ ไม่มีคอนสตรัคโหลดมากเกินไป classmethodและเพื่อที่จะบรรลุเป้าหมายนี้คุณสามารถใช้ ตัวอย่างต่อไปนี้จะอธิบายสิ่งนี้

ลองพิจารณาเรามีPersonระดับซึ่งจะใช้เวลาสองมีปากเสียงfirst_nameและและสร้างตัวอย่างของlast_namePerson

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

2
มันเป็นโพสต์เก่า แต่วิธี pythonic มากขึ้นเพื่อให้บรรลุคอนสตรัคที่ยอมรับข้อโต้แย้งหนึ่งหรือสองจะใช้def __init__(self, first_name, last_name="")แทนวิธีการget_personเรียน และผลลัพธ์จะเหมือนกันทุกประการในกรณีนี้
akarilimano

31

ฉันคิดว่าคำถามที่ดีกว่าคือ "เมื่อใดที่คุณจะใช้ @classmethod vs @staticmethod"

@classmethod ช่วยให้คุณสามารถเข้าถึงสมาชิกส่วนตัวที่เกี่ยวข้องกับคำจำกัดความของคลาสได้อย่างง่ายดาย นี่เป็นวิธีที่ยอดเยี่ยมในการทำซิงเกิลตันหรือคลาสของโรงงานที่ควบคุมจำนวนอินสแตนซ์ของวัตถุที่สร้างขึ้น

@staticmethod ให้ประสิทธิภาพที่เพิ่มขึ้นเล็กน้อย แต่ฉันยังไม่เห็นวิธีการใช้งานที่มีประสิทธิผลของคลาสแบบคงที่ภายในคลาสที่ไม่สามารถทำได้ในรูปแบบฟังก์ชันสแตนด์อโลนนอกคลาส


31

@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 ก็คือเพื่อให้คลาสย่อยสามารถตัดสินใจที่จะเปลี่ยนการใช้งานอาจเป็นเพราะมันเป็นเรื่องทั่วไปและสามารถจัดการกับคลัสเตอร์มากกว่าหนึ่งประเภทดังนั้นเพียงแค่ตรวจสอบชื่อของคลาสที่จะไม่เพียงพอ


28

วิธีการคงที่:

  • ฟังก์ชั่นง่าย ๆ ที่ไม่มีการโต้แย้งตนเอง
  • ทำงานกับคุณลักษณะของคลาส ไม่ได้อยู่ในคุณสมบัติของอินสแตนซ์
  • สามารถถูกเรียกผ่านทั้งคลาสและอินสแตนซ์
  • ฟังก์ชั่นคงที่วิธีการแบบคงที่ () จะใช้ในการสร้างพวกเขา

ประโยชน์ของวิธีการคงที่:

  • มันแปลชื่อฟังก์ชั่นใน classscope
  • มันเลื่อนโค้ดฟังก์ชันใกล้กับตำแหน่งที่ใช้
  • สะดวกกว่าในการนำเข้าเมื่อเทียบกับฟังก์ชั่นระดับโมดูลเนื่องจากแต่ละวิธีไม่จำเป็นต้องนำเข้าเป็นพิเศษ

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

วิธีการเรียน:

  • ฟังก์ชันที่มีอาร์กิวเมนต์แรกเป็น classname
  • สามารถถูกเรียกผ่านทั้งคลาสและอินสแตนซ์
  • เหล่านี้ถูกสร้างขึ้นด้วยฟังก์ชั่นชั้นเรียนวิธีการในการสร้าง

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

22

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

1
ข้อเสียอย่างหนึ่งที่เป็นไปได้ของ metaclass สำหรับเรื่องนี้ที่เกิดขึ้นกับฉันทันทีคือคุณไม่สามารถเรียก classmethod โดยตรงบนอินสแตนซ์ได้ c = C(); c.foo()ยก AttributeError type(c).foo()คุณจะต้องทำ นี่อาจเป็นคุณสมบัติ - ฉันคิดไม่ออกว่าทำไมคุณถึงต้องการ
Aaron Hall

20

คำแนะนำที่ชัดเจนเกี่ยวกับวิธีการใช้วิธีการคงที่คลาสหรือนามธรรมใน Pythonเป็นลิงค์ที่ดีสำหรับหัวข้อนี้และสรุปได้ดังต่อไปนี้

@staticmethodฟังก์ชั่นคืออะไรมากกว่าฟังก์ชั่นที่กำหนดไว้ในชั้นเรียน มันเป็น callable โดยไม่ต้องยกระดับชั้นก่อน ความหมายของมันไม่เปลี่ยนรูปผ่านการสืบทอด

  • Python ไม่จำเป็นต้องสร้างอินสแตนซ์ขอบเขตสำหรับวัตถุ
  • มันลดความสามารถในการอ่านของโค้ดและไม่ขึ้นอยู่กับสถานะของวัตถุเอง

@classmethodฟังก์ชั่นยัง callable โดยไม่ต้องยกระดับชั้นเรียน แต่คำจำกัดความของมันตามชั้นย่อยไม่ใช่ชั้นผู้ปกครองผ่านการสืบทอดสามารถถูกแทนที่โดย subclass นั่นเป็นเพราะอาร์กิวเมนต์แรกสำหรับ@classmethodฟังก์ชั่นจะต้องเป็นcls (คลาส) เสมอ

  • วิธีการจากโรงงานที่ใช้ในการสร้างอินสแตนซ์สำหรับคลาสที่ใช้เช่นการประมวลผลล่วงหน้าบางประเภท
  • วิธีการแบบคงที่เรียกวิธีการแบบคงที่ : ถ้าคุณแยกวิธีการแบบคงที่ในหลายวิธีคงที่คุณไม่ควรยากรหัสชื่อชั้น แต่ใช้วิธีการเรียน

ขอบคุณ @zangw - ฟังก์ชั่นคงที่ไม่สามารถสืบทอดได้คือความแตกต่างที่สำคัญ
hard_working_ant

18

เฉพาะอาร์กิวเมนต์แรกเท่านั้นที่แตกต่าง :

  • วิธีการปกติ: วัตถุปัจจุบันถ้าผ่านโดยอัตโนมัติเป็นอาร์กิวเมนต์แรก (เพิ่มเติม)
  • classmethod: คลาสของวัตถุปัจจุบันจะถูกส่งผ่านโดยอัตโนมัติเป็นอาร์กิวเมนต์กำปั้น (เพิ่มเติม)
  • staticmethod: ไม่มีการขัดแย้งพิเศษจะถูกส่งผ่านโดยอัตโนมัติ สิ่งที่คุณส่งให้ฟังก์ชั่นคือสิ่งที่คุณได้รับ

รายละเอียดเพิ่มเติม ...

วิธีการปกติ

เมื่อมีการเรียกเมธอดของวัตถุมันจะได้รับอาร์กิวเมนต์พิเศษ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)

17

ให้ฉันบอกความคล้ายคลึงกันระหว่างวิธีที่ตกแต่งด้วย @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',)

11

ข้อพิจารณาอีกประการหนึ่งเกี่ยวกับ 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


7

ฉันจะพยายามอธิบายความแตกต่างพื้นฐานโดยใช้ตัวอย่าง

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- วิธีการเรียนไม่ได้ผูกไว้กับวัตถุ แต่ไปเรียน


โฆษณา 2: คุณแน่ใจนะ วิธีการที่คงที่สามารถเรียกวิธีการเรียน? มันไม่มีการอ้างอิงถึงมัน (ไปยังคลาสของมัน)
mirek

7

@classmethod: สามารถใช้เพื่อสร้างการเข้าถึงทั่วโลกที่ใช้ร่วมกันไปยังอินสแตนซ์ทั้งหมดที่สร้างของคลาสนั้น ... เช่นการอัปเดตระเบียนโดยผู้ใช้หลายคน .... ฉันพบว่ามันใช้ ful เมื่อสร้าง singletons เช่นกัน .. : )

วิธี @ คงที่: มีอะไรจะทำอย่างไรกับชั้นเรียนหรืออินสแตนซ์ที่เกี่ยวข้องกับ ... แต่สำหรับการอ่านสามารถใช้วิธีการคงที่


5

คุณอาจต้องการพิจารณาความแตกต่างระหว่าง:

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


5

วิธีการเรียนได้รับชั้นเรียนเป็นอาร์กิวเมนต์แรกโดยปริยายเช่นเดียวกับวิธีการได้รับตัวอย่างเช่น มันเป็นวิธีการที่ถูกผูกไว้กับชั้นเรียนและไม่ได้เป็นวัตถุของชั้นเรียน แต่ก็มีการเข้าถึงสถานะของชั้นเรียนเพราะมันใช้พารามิเตอร์ชั้นเรียนที่ชี้ไปที่ชั้นเรียนและไม่อินสแตนซ์วัตถุ มันสามารถแก้ไขสถานะคลาสที่จะใช้กับอินสแตนซ์ทั้งหมดของคลาส ตัวอย่างเช่นมันสามารถปรับเปลี่ยนตัวแปรคลาสที่จะใช้กับทุกกรณี

ในทางตรงกันข้ามวิธีการคงที่ไม่ได้รับการโต้แย้งครั้งแรกโดยนัยเมื่อเทียบกับวิธีการเรียนหรือวิธีการเช่น และไม่สามารถเข้าถึงหรือแก้ไขสถานะคลาสได้ มันเป็นของคลาสเพราะจากมุมมองการออกแบบที่เป็นวิธีที่ถูกต้อง แต่ในแง่ของฟังก์ชั่นไม่ได้ถูกผูกไว้ที่ runtime กับคลาส

เป็นแนวทางให้ใช้วิธีการคงที่เป็นสาธารณูปโภคใช้วิธีการเรียนเช่นโรงงาน หรืออาจจะกำหนดซิงเกิล และใช้วิธีการอินสแตนซ์เพื่อจำลองสถานะและพฤติกรรมของอินสแตนซ์

หวังว่าฉันจะชัดเจน!


4

ผลงานของฉันแสดงให้เห็นถึงความแตกต่างในหมู่@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)
'''
"""

2

วิธีการเรียนตามชื่อแนะนำจะใช้ในการเปลี่ยนแปลงชั้นเรียนและไม่ใช่วัตถุ ในการเปลี่ยนแปลงคลาสพวกเขาจะปรับเปลี่ยนคลาสคลาส (ไม่ใช่แอตทริบิวต์ของออบเจ็กต์) เนื่องจากเป็นวิธีที่คุณอัพเดตคลาส นี่คือเหตุผลที่วิธีการเรียนใช้คลาส (แสดงโดยอัตภาพโดย '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."

2

วิเคราะห์ @staticmethod อย่างแท้จริงให้ข้อมูลเชิงลึกที่แตกต่างกัน

วิธีการปกติของชั้นเรียนเป็นวิธีแบบไดนามิกโดยนัยซึ่งใช้อินสแตนซ์เป็นอาร์กิวเมนต์แรก
ในทางตรงกันข้าม StaticMethod ไม่ได้ใช้อินสแตนซ์เป็นอาร์กิวเมนต์แรกดังนั้นจะเรียกว่า'คงที่'

สแตติกเมธอดนั้นเป็นฟังก์ชั่นปกติเช่นเดียวกันกับที่อยู่นอกคำจำกัดความของคลาส
มันถูกจัดกลุ่มเข้าชั้นเรียนอย่างใกล้ชิดเพื่อที่จะเข้าใกล้มันมากขึ้นหรือคุณอาจเลื่อนไปรอบ ๆ เพื่อหามัน


2

ฉันคิดว่าให้รุ่น 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

1

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==2self.__class__.b

เราสามารถทำตามสไตล์จูบ (ให้มันง่ายโง่): อย่าใช้ staticmethods และ classmethods ไม่ได้ใช้เรียนได้โดยไม่ต้อง instantiating self.attribute(s)พวกเขาเพียงแอตทริบิวต์วัตถุของการเข้าถึง มีภาษาที่ใช้ OOP ด้วยวิธีนี้และฉันคิดว่าไม่ใช่ความคิดที่ไม่ดี :)


สิ่งสำคัญอีกอย่างหนึ่งสำหรับ classmethods: หากคุณปรับเปลี่ยนแอตทริบิวต์ในเมธอดคลาสวัตถุที่มีอยู่ทั้งหมดของคลาสนี้ซึ่งไม่ได้ตั้งค่าคุณลักษณะนี้อย่างชัดเจนจะมีค่าที่แก้ไข
mirek

-4

วิธีที่เหมือนกันอย่างรวดเร็วของการแฮ็กขึ้นใน iPython เผยให้เห็นว่า@staticmethodได้รับประสิทธิภาพที่เพิ่มขึ้นเล็กน้อย (เป็นนาโนวินาที) แต่อย่างอื่นดูเหมือนว่าจะไม่สามารถใช้งานได้ นอกจากนี้การเพิ่มประสิทธิภาพใด ๆ อาจจะถูกลบออกโดยงานเพิ่มเติมของการประมวลผลวิธีการstaticmethod()ในระหว่างการรวบรวม (ซึ่งเกิดขึ้นก่อนการเรียกใช้รหัสใด ๆ เมื่อคุณเรียกใช้สคริปต์)

เพื่อประโยชน์ในการอ่านโค้ดฉันจะหลีกเลี่ยง@staticmethodเว้นแต่ว่าวิธีการของคุณจะถูกใช้สำหรับการทำงานที่มีจำนวนนาโนวินาที


7
"มิฉะนั้นดูเหมือนว่าจะไม่ใช้งานฟังก์ชั่น": ไม่จริงอย่างเคร่งครัด ดูการสนทนาข้างต้น
Keith Pinson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.