Nhận lớp mà phương thức xác định


89

Làm cách nào để lấy lớp đã định nghĩa một phương thức trong Python?

Tôi muốn ví dụ sau để in " __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)

Bạn đang sử dụng phiên bản Python nào? Trước 2.2, bạn có thể sử dụng im_class, nhưng điều đó đã được thay đổi để hiển thị kiểu của đối tượng tự ràng buộc.
Kathy Van Đá

1
Tốt để biết. Nhưng tôi đang sử dụng 2.6.
Jesse Aldridge

Câu trả lời:


71
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
Xin cảm ơn, nó đã có thể lấy cho tôi một thời gian để con số này ra một mình
David

Hãy cẩn thận, không phải tất cả các lớp đều triển khai __dict__! Đôi khi __slots__được sử dụng. Có lẽ tốt hơn nên sử dụng getattrđể kiểm tra xem phương thức có trong lớp hay không.
Mã CodieMonkey

16
Đối với Python 3, vui lòng tham khảo câu trả lời này .
Yoel

27
Tôi nhận được:'function' object has no attribute 'im_class'
Zitrax

5
Trong Python 2.7, nó không hoạt động. Cùng một lỗi về việc thiếu 'im_class'.
RedX

8

Cảm ơn Sr2222 đã chỉ ra rằng tôi đã thiếu điểm ...

Đây là cách tiếp cận đã sửa chữa giống như của Alex nhưng không yêu cầu nhập bất kỳ thứ gì. Tôi không nghĩ rằng đó là một cải tiến, trừ khi có một hệ thống phân cấp lớn của các lớp kế thừa vì cách tiếp cận này dừng ngay khi tìm thấy lớp xác định, thay vì trả lại toàn bộ kế thừa như getmrohiện tại. Như đã nói, đây là một kịch bản rất khó xảy ra.

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

Và ví dụ:

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

Giải pháp Alex trả về kết quả tương tự. Miễn là phương pháp tiếp cận Alex có thể được sử dụng, tôi sẽ sử dụng nó thay vì phương pháp này.


Cls().meth.__self__chỉ cung cấp cho bạn ví dụ của Clscái đó được liên kết với phiên bản cụ thể của meth. Nó tương tự như Cls().meth.im_class. Nếu bạn có class SCls(Cls), SCls().meth.__self__bạn sẽ nhận được một SClsphiên bản, không phải một Clsphiên bản. Những gì OP muốn là có được Cls, thứ mà dường như nó chỉ có sẵn bằng cách đi bộ MRO như @Alex Martelli đã làm.
Silas Ray

@ sr2222 Bạn nói đúng. Tôi đã sửa đổi câu trả lời khi tôi đã bắt đầu mặc dù tôi nghĩ rằng giải pháp Alex nhỏ gọn hơn.
estani

1
Đó là một giải pháp tốt nếu bạn cần tránh nhập khẩu, nhưng vì về cơ bản bạn chỉ đang triển khai lại MRO, nó không đảm bảo hoạt động mãi mãi. MRO có thể sẽ giữ nguyên, nhưng nó đã được thay đổi một lần trong quá khứ của Python, và nếu nó được thay đổi một lần nữa, mã này sẽ dẫn đến các lỗi tinh vi, lan tràn.
Silas Ray

Hết lần này đến lần khác, "kịch bản rất khó xảy ra" vẫn xảy ra trong lập trình. Ít khi gây ra tai họa. Nói chung, mô hình tư duy "XY.Z% nó sẽ không bao giờ xảy ra" là một công cụ tư duy cực kỳ tệ hại khi làm mã hóa. Đừng sử dụng nó. Viết mã đúng 100%.
ulidtko

@ulidtko Tôi nghĩ bạn đã đọc sai lời giải thích. Nó không phải về tính đúng đắn mà là tốc độ. Không có giải pháp "hoàn hảo" phù hợp với tất cả các trường hợp, nếu không, chẳng hạn như sẽ chỉ có một thuật toán sắp xếp. Giải pháp được đề xuất ở đây nên nhanh hơn trong trường hợp "hiếm". Vì tốc độ chiếm 99% trong tất cả các trường hợp sau khi có thể đọc được, giải pháp này có thể là giải pháp tốt hơn trong trường hợp hiếm gặp "chỉ" đó. Mã, trong trường hợp bạn không đọc nó, là đúng 100%, nếu đó là điều bạn lo sợ.
estani

6

Tôi không biết tại sao không ai đưa ra điều này hoặc tại sao câu trả lời hàng đầu có 50 lượt ủng hộ khi nó chậm như địa ngục, nhưng bạn cũng có thể làm như sau:

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

Đối với python 3, tôi tin rằng điều này đã thay đổi và bạn sẽ cần phải xem xét .__qualname__.


2
Hừ! Tôi không nhìn thấy những lớp xác định khi tôi làm điều đó trong python 2.7 - Tôi nhận lớp mà phương pháp này đã được kêu gọi, không phải là một trong những nơi nó được định nghĩa ...
F1Rumors

5

Trong Python 3, nếu bạn cần đối tượng lớp thực tế, bạn có thể làm:

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

Nếu hàm có thể thuộc về một lớp lồng nhau, bạn sẽ cần phải lặp lại như sau:

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

1

Tôi bắt đầu làm điều gì đó tương tự, về cơ bản, ý tưởng đang kiểm tra bất cứ khi nào một phương thức trong lớp cơ sở được triển khai hay không trong lớp con. Hóa ra theo cách tôi đã làm ban đầu, tôi không thể phát hiện ra khi nào một lớp trung gian đang thực sự triển khai phương thức này.

Cách giải quyết của tôi cho nó thực sự khá đơn giản; thiết lập một thuộc tính phương thức và kiểm tra sự hiện diện của nó sau đó. Đây là đơn giản hóa toàn bộ:

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

CẬP NHẬT: Thực tế cuộc gọi method()từrun_method() (đó không phải là linh hồn sao?) Và nó truyền tất cả các đối số chưa được sửa đổi vào phương thức.

Tái bút: Câu trả lời này không trực tiếp trả lời câu hỏi. IMHO có hai lý do mà người ta muốn biết lớp nào đã định nghĩa một phương thức; đầu tiên là trỏ ngón tay vào một lớp trong mã gỡ lỗi (chẳng hạn như trong xử lý ngoại lệ), và thứ hai là xác định xem phương thức đã được triển khai lại hay chưa (trong đó phương thức là một sơ khai được lập trình viên thực hiện). Câu trả lời này giải quyết trường hợp thứ hai đó theo một cách khác.


1

Python 3

Giải quyết nó theo một cách rất đơn giản:

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

Điều này cho

'FooClass.foo_method'

Tách trên dấu chấm để lấy lớp và tên hàm riêng biệt


Wow thật là tiết kiệm!
user3712978
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.