การเรียกคลาสคงที่วิธีการภายในร่างกายของชั้นเรียน?


159

เมื่อฉันพยายามที่จะใช้วิธีการคงที่จากภายในร่างกายของชั้นเรียนและกำหนดวิธีการคงที่โดยใช้staticmethodฟังก์ชั่นในตัวเป็นมัณฑนากรเช่นนี้:

class Klass(object):

    @staticmethod  # use as decorator
    def _stat_func():
        return 42

    _ANS = _stat_func()  # call the staticmethod

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

ฉันได้รับข้อผิดพลาดต่อไปนี้:

Traceback (most recent call last):<br>
  File "call_staticmethod.py", line 1, in <module>
    class Klass(object): 
  File "call_staticmethod.py", line 7, in Klass
    _ANS = _stat_func() 
  TypeError: 'staticmethod' object is not callable

ฉันเข้าใจว่าทำไมสิ่งนี้จึงเกิดขึ้น (การเชื่อมโยงตัวบอกคำอธิบาย)และสามารถแก้ไขได้ด้วยการแปลง_stat_func()เป็นวิธีการคงที่ด้วยตนเองหลังจากการใช้งานครั้งสุดท้ายเช่น:

class Klass(object):

    def _stat_func():
        return 42

    _ANS = _stat_func()  # use the non-staticmethod version

    _stat_func = staticmethod(_stat_func)  # convert function to a static method

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

ดังนั้นคำถามของฉันคือ:

มีวิธีที่ดีกว่าในการทำความสะอาดหรือ "Pythonic" มากกว่านี้หรือไม่?


4
หากคุณกำลังถามเกี่ยวกับ Pythonicity คำแนะนำมาตรฐานก็คือห้ามใช้staticmethodเลย พวกเขามักจะมีประโยชน์มากขึ้นเป็นฟังก์ชั่นระดับโมดูลซึ่งในกรณีที่ปัญหาของคุณไม่เป็นปัญหา classmethodในทางกลับกัน ...
เบนจามินฮอดจ์สัน

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

คำตอบ:


180

staticmethodเห็นได้ชัดว่าวัตถุมี__func__คุณลักษณะที่เก็บฟังก์ชันดิบดั้งเดิม (ทำให้รู้สึกว่าพวกเขาต้อง) ดังนั้นจะใช้งานได้:

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    _ANS = stat_func.__func__()  # call the staticmethod

    def method(self):
        ret = Klass.stat_func()
        return ret

นอกเหนือจากนั้นแม้ว่าฉันสงสัยว่าวัตถุแบบคงที่มีคุณลักษณะบางอย่างที่เก็บฟังก์ชั่นดั้งเดิม แต่ฉันก็ไม่ทราบถึงความเฉพาะเจาะจง ในจิตวิญญาณของการสอนใครบางคนให้ตกปลาแทนที่จะให้ปลาแก่พวกเขานี่คือสิ่งที่ฉันทำเพื่อตรวจสอบและค้นหา (C&P จากเซสชัน Python ของฉัน):

>>> class Foo(object):
...     @staticmethod
...     def foo():
...         return 3
...     global z
...     z = foo

>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>

การขุดที่คล้ายกันในเซสชันการโต้ตอบ ( dirมีประโยชน์มาก) มักจะสามารถแก้ปัญหาได้อย่างรวดเร็ว


การอัปเดตที่ดี ... ฉันเพิ่งจะถามว่าคุณรู้เรื่องนี้อย่างไรเนื่องจากฉันไม่เห็นมันในเอกสารประกอบ - ทำให้ฉันกังวลเล็กน้อยเกี่ยวกับการใช้เพราะอาจเป็น "รายละเอียดการใช้งาน"
martineau

หลังจากอ่านเพิ่มเติมฉันเห็นว่า__func__เป็นเพียงชื่ออื่นim_funcและถูกเพิ่มใน Py 2.6 สำหรับ Python 3 ไปข้างหน้าเข้ากันได้
martineau

2
ฉันเข้าใจแล้วในทางเทคนิคแล้วมันไม่มีเอกสารในบริบทนี้
martineau

1
@AkshayHazari __func__คุณลักษณะของวิธีการแบบคงที่ทำให้คุณได้รับการอ้างอิงถึงฟังก์ชั่นเดิมเหมือนกับว่าคุณไม่เคยใช้staticmethodมัณฑนากร __func__ดังนั้นหากการทำงานของคุณต้องมีข้อโต้แย้งที่คุณจะต้องผ่านพวกเขาเมื่อโทร ข้อความแสดงข้อผิดพลาดที่คุณฟังดูเหมือนคุณยังไม่ได้ให้ข้อโต้แย้งใด ๆ หากตัวอย่างstat_funcในโพสต์นี้มีการโต้แย้งสองข้อคุณจะใช้_ANS = stat_func.__func__(arg1, arg2)
เบ็

1
@ AkshayHazari ฉันไม่แน่ใจว่าฉันเข้าใจ พวกเขาสามารถเป็นตัวแปรพวกเขาจะต้องเป็นตัวแปรในขอบเขต: ทั้งที่กำหนดไว้ก่อนหน้านี้ในขอบเขตเดียวกันที่กำหนดระดับ (มักจะทั่วโลก) หรือกำหนดไว้ก่อนหน้าภายในขอบเขตของชั้นเรียน ( stat_funcตัวเองเป็นตัวแปร) คุณหมายถึงคุณไม่สามารถใช้คุณสมบัติอินสแตนซ์ของคลาสที่คุณกำหนดได้หรือไม่? นั่นเป็นเรื่องจริง แต่ก็ไม่แปลกใจ เราไม่ได้มีอินสแตนซ์ของระดับชั้นตั้งแต่เรายังคงกำหนดชั้น! แต่อย่างไรก็ตามฉันแค่หมายถึงพวกเขาว่าเป็นสิ่งที่คุณต้องการผ่านการโต้เถียง; คุณสามารถใช้ตัวอักษรที่นั่น
Ben

24

นี่คือวิธีที่ฉันชอบ:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

    def method(self):
        return self.__class__.stat_func() + self.__class__._ANS

ฉันชอบการแก้ปัญหานี้ไปKlass.stat_funcเพราะหลักการแห้ง เตือนฉันถึงสาเหตุที่มีsuper() Python 3 ใหม่ :)

แต่ฉันเห็นด้วยกับคนอื่น ๆ มักจะเป็นทางเลือกที่ดีที่สุดคือการกำหนดฟังก์ชั่นระดับโมดูล

เช่นกับ@staticmethodฟังก์ชั่นการเรียกซ้ำอาจไม่ดีมาก (คุณจะต้องทำลายหลักการ DRY โดยการโทรKlass.stat_funcภายในKlass.stat_func) นั่นเป็นเพราะคุณไม่มีการอ้างอิงถึงselfวิธีการคงที่ภายใน ด้วยฟังก์ชั่นระดับโมดูลทุกอย่างจะดูดี


ในขณะที่ฉันยอมรับว่าการใช้self.__class__.stat_func()ในวิธีการปกติมีข้อดี (DRY และทั้งหมด) มากกว่าการใช้Klass.stat_func()นั่นไม่ใช่หัวข้อของคำถามของฉัน - ในความเป็นจริงฉันหลีกเลี่ยงการใช้แบบเดิมเพื่อไม่ให้เกิดปัญหาความไม่สอดคล้องกัน
martineau

1
มันไม่ได้เป็นปัญหา DRY ต่อ se จริงๆ self.__class__จะดีกว่าเพราะถ้าแทนที่คลาสย่อยstat_funcแล้วจะโทรของประเภทรองSubclass.method stat_funcแต่จะซื่อสัตย์อย่างสมบูรณ์ในสถานการณ์นั้นดีกว่ามากเพียงแค่ใช้วิธีการจริงแทนแบบคงที่
asmeurer

@asmeurer: ฉันไม่สามารถใช้วิธีการจริงได้เนื่องจากยังไม่มีการสร้างชั้นเรียน - พวกเขาไม่สามารถทำได้เนื่องจากชั้นเรียนยังไม่ได้กำหนดอย่างสมบูรณ์
martineau

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

11

สิ่งที่เกี่ยวกับการฉีดแอตทริบิวต์ class หลังจากที่คำจำกัดความของชั้นเรียน?

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    def method(self):
        ret = Klass.stat_func()
        return ret

Klass._ANS = Klass.stat_func()  # inject the class attribute with static method value

1
นี่คล้ายกับการพยายามครั้งแรกของฉันในที่ทำงาน แต่ฉันต้องการสิ่งที่อยู่ในชั้นเรียน ... บางส่วนเนื่องจากคุณลักษณะที่เกี่ยวข้องมีขีดเส้นใต้และเป็นส่วนตัว
martineau

11

นี่คือสาเหตุที่ staticmethod เป็น descriptor และต้องการคุณลักษณะระดับคลาสเพื่อเรียกใช้โปรโตคอล descriptor และรับ callable จริง

จากซอร์สโค้ด:

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

แต่ไม่ใช่โดยตรงจากภายในชั้นเรียนในขณะที่มันถูกกำหนด

แต่ในฐานะผู้วิจารณ์คนหนึ่งพูดถึงนี่ไม่ใช่การออกแบบ "Pythonic" เลย เพียงใช้ฟังก์ชั่นระดับโมดูลแทน


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

Staticmethod ต้องการวัตถุคลาตัวเองทำงานอย่างถูกต้อง แต่ถ้าคุณโทรจากภายในคลาสที่ระดับคลาสคลาสนั้นจะไม่ถูกกำหนดอย่างสมบูรณ์ในจุดนั้น ดังนั้นคุณยังไม่สามารถอ้างอิงได้ มันก็โอเคที่จะเรียกวิธีการคงที่จากนอกชั้นเรียนหลังจากที่มันถูกกำหนด แต่ " Pythonic " นั้นไม่ได้นิยามอย่างชัดเจนเหมือนกับสุนทรียศาสตร์ ฉันสามารถบอกคุณได้ว่าความประทับใจแรกของฉันที่มีต่อรหัสนั้นไม่ดี
Keith

ความประทับใจของเวอร์ชันใด (หรือทั้งคู่)? และทำไมโดยเฉพาะ
martineau

ฉันเดาได้เลยว่าเป้าหมายสูงสุดของคุณคืออะไร แต่สำหรับฉันแล้วคุณต้องการคุณลักษณะระดับชั้นที่มีพลัง ดูเหมือนว่างานสำหรับ metaclasses แต่อีกครั้งอาจมีวิธีที่ง่ายกว่าหรืออีกวิธีหนึ่งในการดูการออกแบบที่กำจัดและลดความซับซ้อนโดยไม่ต้องเสียสละการทำงาน
Keith

3
ไม่สิ่งที่ฉันต้องการทำจริงๆแล้วค่อนข้างเรียบง่าย - ซึ่งก็คือการแยกเอารหัสทั่วไปและนำมาใช้ซ้ำทั้งสองเพื่อสร้างแอตทริบิวต์คลาสส่วนตัวในเวลาที่สร้างคลาสและหลังจากนั้นภายในหนึ่งหรือหลายวิธี รหัสทั่วไปนี้ไม่มีประโยชน์นอกห้องเรียนดังนั้นฉันจึงต้องการให้มันเป็นส่วนหนึ่งของมัน การใช้ metaclass (หรือมัณฑนากรในห้องเรียน) อาจใช้งานได้ แต่ดูเหมือนจะเกินความจำเป็นสำหรับบางสิ่งที่ควรจะทำได้ง่าย IMHO
martineau

8

แล้วโซลูชันนี้ล่ะ? ไม่ต้องอาศัยความรู้ใน@staticmethodการนำมัณฑนากรมาใช้ ชั้นใน StaticMethod เล่นเป็นภาชนะของฟังก์ชั่นเริ่มต้นคงที่

class Klass(object):

    class StaticMethod:
        @staticmethod  # use as decorator
        def _stat_func():
            return 42

    _ANS = StaticMethod._stat_func()  # call the staticmethod

    def method(self):
        ret = self.StaticMethod._stat_func() + Klass._ANS
        return ret

2
+1 สำหรับความคิดสร้างสรรค์ แต่ฉันไม่กังวลเกี่ยวกับการใช้__func__เพราะตอนนี้เป็นเอกสารอย่างเป็นทางการแล้ว (ดูหัวข้อการเปลี่ยนแปลงภาษาอื่น ๆของ Python 2.7 ในภาษาอื่นและการอ้างอิงถึงฉบับที่ 5982 ) โซลูชันของคุณสามารถพกพาได้มากกว่าเดิมเนื่องจากอาจใช้กับ Python เวอร์ชันก่อนถึง 2.6 ได้ (เมื่อ__func__ถูกนำมาใช้เป็นคำพ้องความหมายเป็นครั้งแรกim_func)
martineau

นี่เป็นทางออกเดียวที่ทำงานกับ Python 2.6
benselme

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