รับคลาสที่กำหนดวิธีการ


89

ฉันจะรับคลาสที่กำหนดเมธอดใน Python ได้อย่างไร

ฉันต้องการให้ตัวอย่างต่อไปนี้พิมพ์ " __main__.FooClass":

class FooClass:
    def foo_method(self):
        print "foo"

class BarClass(FooClass):
    pass

bar = BarClass()
print get_class_that_defined_method(bar.foo_method)

คุณใช้ Python เวอร์ชันใด ก่อน 2.2 คุณสามารถใช้ im_class ได้ แต่มีการเปลี่ยนแปลงเพื่อแสดงประเภทของวัตถุที่ถูกผูกไว้
Kathy Van Stone

1
ดีแล้วที่รู้. แต่ฉันใช้ 2.6
Jesse Aldridge

คำตอบ:


70
import inspect

def get_class_that_defined_method(meth):
    for cls in inspect.getmro(meth.im_class):
        if meth.__name__ in cls.__dict__: 
            return cls
    return None

1
ขอบคุณคงต้องใช้เวลาสักพักในการคิดออกด้วยตัวเอง
David

ระวังไม่ใช่ทุกชั้นเรียนใช้__dict__! บางครั้ง__slots__ก็ใช้ น่าจะดีกว่าgetattrถ้าใช้เพื่อทดสอบว่าเมธอดอยู่ในคลาสหรือไม่
Codie CodeMonkey

16
สำหรับPython 3โปรดดูคำตอบนี้
Yoel

27
ฉันได้รับ:'function' object has no attribute 'im_class'
Zitrax

5
ใน Python 2.7 ไม่ทำงาน ข้อผิดพลาดเดียวกันเกี่ยวกับ "im_class" ที่หายไป
RedX

8

ขอบคุณ Sr2222 ที่ชี้ว่าฉันพลาดประเด็น ...

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

def get_class_that_defined_method(method):
    method_name = method.__name__
    if method.__self__:    
        classes = [method.__self__.__class__]
    else:
        #unbound method
        classes = [method.im_class]
    while classes:
        c = classes.pop()
        if method_name in c.__dict__:
            return c
        else:
            classes = list(c.__bases__) + classes
    return None

และตัวอย่าง:

>>> class A(object):
...     def test(self): pass
>>> class B(A): pass
>>> class C(B): pass
>>> class D(A):
...     def test(self): print 1
>>> class E(D,C): pass

>>> get_class_that_defined_method(A().test)
<class '__main__.A'>
>>> get_class_that_defined_method(A.test)
<class '__main__.A'>
>>> get_class_that_defined_method(B.test)
<class '__main__.A'>
>>> get_class_that_defined_method(C.test)
<class '__main__.A'>
>>> get_class_that_defined_method(D.test)
<class '__main__.D'>
>>> get_class_that_defined_method(E().test)
<class '__main__.D'>
>>> get_class_that_defined_method(E.test)
<class '__main__.D'>
>>> E().test()
1

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


Cls().meth.__self__เพียงแค่ช่วยให้คุณมีตัวอย่างของที่ถูกผูกไว้กับกรณีเฉพาะที่Cls มันคล้ายกับmeth Cls().meth.im_classหากคุณมีclass SCls(Cls), SCls().meth.__self__คุณจะได้รับSClsเช่นไม่ได้เป็นClsเช่น สิ่งที่ OP ต้องการคือได้รับClsซึ่งดูเหมือนว่าจะทำได้โดยการเดิน MRO ตามที่ @Alex Martelli ทำเท่านั้น
Silas Ray

@ sr2222 คุณพูดถูก ฉันได้แก้ไขคำตอบตามที่ได้เริ่มไปแล้วแม้ว่าฉันคิดว่าโซลูชันของ Alex นั้นกะทัดรัดกว่า
estani

1
เป็นทางออกที่ดีหากคุณต้องการหลีกเลี่ยงการนำเข้า แต่เนื่องจากโดยพื้นฐานแล้วคุณเพิ่งนำ MRO มาใช้ใหม่จึงไม่รับประกันว่าจะทำงานได้ตลอดไป MRO อาจจะยังคงเหมือนเดิม แต่มีการเปลี่ยนแปลงไปแล้วครั้งหนึ่งในอดีตของ Python และหากมีการเปลี่ยนแปลงอีกครั้งรหัสนี้จะส่งผลให้เกิดข้อบกพร่องที่ละเอียดอ่อนและแพร่หลาย
Silas Ray

ครั้งแล้วครั้งเล่า "สถานการณ์ที่ไม่น่าเกิดขึ้นมาก" จะเกิดขึ้นในการเขียนโปรแกรม ไม่ค่อยก่อให้เกิดภัยพิบัติ โดยทั่วไปแล้วรูปแบบการคิด"XY.Z% มันจะไม่เกิดขึ้น"เป็นเครื่องมือคิดที่ล่อแหลมอย่างมากเมื่อทำการเข้ารหัส อย่าใช้มัน. เขียนรหัสที่ถูกต้อง 100%
ulidtko

@ulidtko ฉันคิดว่าคุณอ่านคำอธิบายผิด ไม่เกี่ยวกับความถูกต้อง แต่ความเร็ว ไม่มีโซลูชันที่ "สมบูรณ์แบบ" ที่เหมาะกับทุกกรณีไม่เช่นนั้นจะมีเพียงอัลกอริทึมการเรียงลำดับเดียว วิธีแก้ปัญหาที่เสนอในที่นี้ควรเร็วกว่าในกรณี "หายาก" เนื่องจากความเร็วมีเปอร์เซ็นต์ 99% ของทุกกรณีหลังจากอ่านได้โซลูชันนี้จึงอาจเป็นทางออกที่ดีกว่าใน "เฉพาะ" กรณีที่หายากนั้น ในกรณีที่คุณไม่ได้อ่านรหัสนั้นถูกต้อง 100% หากนั่นคือสิ่งที่คุณกลัว
estani

6

ฉันไม่รู้ว่าทำไมไม่เคยมีใครพูดถึงเรื่องนี้หรือทำไมคำตอบยอดนิยมถึงมี 50 upvotes เมื่อมันช้าเหมือนนรก แต่คุณสามารถทำสิ่งต่อไปนี้ได้:

def get_class_that_defined_method(meth):
    return meth.im_class.__name__

สำหรับหลาม 3 .__qualname__ผมเชื่อว่าการเปลี่ยนแปลงนี้และคุณจะต้องดูเป็น


2
อืม. ฉันไม่เห็นคลาสที่กำหนดเมื่อฉันทำเช่นนั้นใน python 2.7 - ฉันได้รับคลาสที่เรียกใช้เมธอดไม่ใช่คลาสที่กำหนดไว้ ...
F1Rumors

5

ใน Python 3 หากคุณต้องการคลาสออบเจ็กต์จริงคุณสามารถทำได้:

import sys
f = Foo.my_function
vars(sys.modules[f.__module__])[f.__qualname__.split('.')[0]]  # Gets Foo object

หากฟังก์ชันสามารถเป็นของคลาสที่ซ้อนกันได้คุณจะต้องวนซ้ำดังนี้:

f = Foo.Bar.my_function
vals = vars(sys.modules[f.__module__])
for attr in f.__qualname__.split('.')[:-1]:
    vals = vals[attr]
# vals is now the class Foo.Bar

2

Python 3

แก้ไขได้ด้วยวิธีง่ายๆ:

str(bar.foo_method).split(" ", 3)[-2]

สิ่งนี้ให้

'FooClass.foo_method'

แยกจุดเพื่อรับคลาสและชื่อฟังก์ชันแยกกัน


ว้าวช่างประหยัด!
user3712978

1

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

วิธีแก้ปัญหาของฉันมันค่อนข้างง่ายจริงๆ การตั้งค่าแอตทริบิวต์วิธีการและทดสอบการมีอยู่ในภายหลัง นี่คือความเรียบง่ายของสิ่งทั้งหมด:

class A():
    def method(self):
        pass
    method._orig = None # This attribute will be gone once the method is implemented

    def run_method(self, *args, **kwargs):
        if hasattr(self.method, '_orig'):
            raise Exception('method not implemented')
        self.method(*args, **kwargs)

class B(A):
    pass

class C(B):
    def method(self):
        pass

class D(C):
    pass

B().run_method() # ==> Raises Exception: method not implemented
C().run_method() # OK
D().run_method() # OK

UPDATE: โทรmethod()จากrun_method() (นั่นไม่ใช่วิญญาณเหรอ) และส่งผ่านข้อโต้แย้งทั้งหมดที่ไม่มีการแก้ไขไปยังวิธีการ

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

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