Cách nhanh nhất để kiểm tra nếu một lớp có một hàm được định nghĩa là gì?


132

Tôi đang viết một thuật toán tìm kiếm không gian trạng thái AI và tôi có một lớp chung có thể được sử dụng để nhanh chóng thực hiện thuật toán tìm kiếm. Một lớp con sẽ xác định các hoạt động cần thiết và thuật toán thực hiện phần còn lại.

Đây là nơi tôi gặp khó khăn: Tôi muốn tránh tái tạo trạng thái cha mẹ nhiều lần, vì vậy tôi có chức năng sau, trả về các hoạt động có thể được áp dụng hợp pháp cho bất kỳ trạng thái nào:

def get_operations(self, include_parent=True):
    ops = self._get_operations()
    if not include_parent and self.path.parent_op:
        try:
            parent_inverse = self.invert_op(self.path.parent_op)
            ops.remove(parent_inverse)
        except NotImplementedError:
            pass
    return ops

Và hàm invert_op ném theo mặc định.

Có cách nào nhanh hơn để kiểm tra xem hàm không được xác định hơn là bắt ngoại lệ không?

Tôi đã suy nghĩ điều gì đó về các dòng kiểm tra để trình bày trong dir, nhưng điều đó có vẻ không đúng. hasattr được thực hiện bằng cách gọi getattr và kiểm tra nếu nó tăng, đó không phải là điều tôi muốn.


8
"hasattr được thực hiện bằng cách gọi getattr và kiểm tra xem nó có tăng không, đó không phải là điều tôi muốn." Tại sao không? Tại sao bạn quan tâm những gì thực hiện?
gièm pha

4
has_op = lambda obj, op: callable(getattr(obj, op, None))
samplebias

1
Hãy thử : hasattr(connection, 'invert_opt').
kenorb

Câu trả lời:


204

Có, sử dụng getattr()để lấy thuộc tính và callable()để xác minh đó là một phương thức:

invert_op = getattr(self, "invert_op", None)
if callable(invert_op):
    invert_op(self.path.parent_op)

Lưu ý rằng getattr()thông thường sẽ ném ngoại lệ khi thuộc tính không tồn tại. Tuy nhiên, nếu bạn chỉ định một giá trị mặc định ( Nonetrong trường hợp này), nó sẽ trả về giá trị đó.


3
Cũng lưu ý rằng việc triển khai getattrtrong trường hợp này bắt đầu một ngoại lệ một cách im lặng và trả về giá trị mặc định, giống như hasattr, điều mà OP đã vì một số lý do chống lại.
Santa

3
Điều gì xảy ra nếu hàm không nằm trong lớp đó, nhưng trên lớp cha?. Trong trường hợp này, tôi nhận được True, ngay cả khi trẻ em không bao giờ thực hiện chức năng đó (sử dụng hasattr)
darkgaze

46

Nó hoạt động trong cả Python 2 và Python 3

hasattr(connection, 'invert_opt')

hasattrtrả về Truenếu đối tượng kết nối có chức năng invert_optđược xác định. Đây là tài liệu để bạn chăn thả

https://docs.python.org/2/l Library / fiances.html # hasattr https://docs.python.org/3/l Library / fiances.html # hasattr


5
Mặc dù mã được đánh giá cao, nó phải luôn có một lời giải thích đi kèm. Điều này không phải là dài nhưng nó được mong đợi.
peterh - Tái lập Monica

tốt, bạn có thể chỉ vào một bài viết mặc dù nó sẽ không bị tổn thương :)
Vitaliy Terziev

5
Điều này cũng trả về True nếu kết nối có thuộc tính connection.invert_opt = 'foo'.
Robert Hönig

20

Có cách nào nhanh hơn để kiểm tra xem hàm không được xác định hơn là bắt ngoại lệ không?

Tại sao bạn chống lại điều đó? Trong hầu hết các trường hợp Pythonic, tốt hơn là yêu cầu sự tha thứ hơn là sự cho phép. ;-)

hasattr được thực hiện bằng cách gọi getattr và kiểm tra nếu nó tăng, đó không phải là điều tôi muốn.

Một lần nữa, tại sao vậy? Sau đây là khá Pythonic:

    try:
        invert_op = self.invert_op
    except AttributeError:
        pass
    else:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

Hoặc là,

    # if you supply the optional `default` parameter, no exception is thrown
    invert_op = getattr(self, 'invert_op', None)  
    if invert_op is not None:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

Lưu ý, tuy nhiên, điều đó getattr(obj, attr, default)về cơ bản được thực hiện bằng cách bắt một ngoại lệ, quá. Không có gì sai với điều đó ở vùng đất Python!


4

Các câu trả lời ở đây kiểm tra xem một chuỗi có phải là tên của một thuộc tính của đối tượng không. Một bước bổ sung (sử dụng có thể gọi được) là cần thiết để kiểm tra xem thuộc tính có phải là một phương thức hay không.

Vì vậy, nó nắm rõ: cách nhanh nhất để kiểm tra xem một đối tượng obj có thuộc tính attrib không. Câu trả lời là

'attrib' in obj.__dict__

Điều này là như vậy bởi vì một dict băm các phím của nó để kiểm tra sự tồn tại của khóa là nhanh chóng.

Xem so sánh thời gian dưới đây.

>>> class SomeClass():
...         pass
...
>>> obj = SomeClass()
>>>
>>> getattr(obj, "invert_op", None)
>>>
>>> %timeit getattr(obj, "invert_op", None)
1000000 loops, best of 3: 723 ns per loop
>>> %timeit hasattr(obj, "invert_op")
The slowest run took 4.60 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 674 ns per loop
>>> %timeit "invert_op" in obj.__dict__
The slowest run took 12.19 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 176 ns per loop

Điều này thất bại trên các lớp sử dụng __slots__. __slots__giúp tăng tốc truy cập thuộc tính ~ 10%. stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ

3

Tôi thích câu trả lời của Nathan Ostgard và tôi đã bình chọn nó. Nhưng một cách khác bạn có thể giải quyết vấn đề của mình là sử dụng một trình trang trí ghi nhớ, sẽ lưu trữ kết quả của lệnh gọi hàm. Vì vậy, bạn có thể tiếp tục và có một chức năng đắt tiền để tìm ra một cái gì đó, nhưng sau đó khi bạn gọi nó nhiều lần thì các cuộc gọi tiếp theo rất nhanh; phiên bản ghi nhớ của hàm tìm kiếm các đối số trong một dict, tìm kết quả trong dict từ khi hàm thực tế tính kết quả và trả về kết quả ngay lập tức.

Dưới đây là một công thức cho một trang trí ghi nhớ được gọi là "lru_cache" của Raymond Hettinger. Phiên bản này hiện là tiêu chuẩn trong mô-đun funcools trong Python 3.2.

http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/

http://docs.python.org/release/3.2/l Library / funcools.html


2

Giống như bất cứ điều gì trong Python, nếu bạn cố gắng hết sức, bạn có thể lấy hết can đảm và làm điều gì đó thực sự khó chịu. Bây giờ, đây là phần khó chịu:

def invert_op(self, op):
    raise NotImplementedError

def is_invert_op_implemented(self):
    # Only works in CPython 2.x of course
    return self.invert_op.__code__.co_code == 't\x00\x00\x82\x01\x00d\x00\x00S'

Xin hãy giúp chúng tôi, hãy tiếp tục làm những gì bạn có trong câu hỏi của bạn và KHÔNG bao giờ sử dụng điều này trừ khi bạn ở trong nhóm PyPy đột nhập vào trình thông dịch Python. Những gì bạn có trên đó là Pythonic, những gì tôi có ở đây là EVIL thuần túy .


Điều này sẽ đúng nếu phương thức đưa ra bất kỳ ngoại lệ nào. Bạn cũng nên kiểm tra xem co_namescó bằng không ('NotImplementedError',). Tôi không chắc chắn nếu điều này làm cho nó ít nhiều ác, tuy nhiên.
kindall

1

Bạn cũng có thể đi qua lớp:

import inspect


def get_methods(cls_):
    methods = inspect.getmembers(cls_, inspect.isfunction)
    return dict(methods)

# Example
class A(object):
    pass

class B(object):
    def foo():
        print('B')


# If you only have an object, you can use `cls_ = obj.__class__`
if 'foo' in get_methods(A):
    print('A has foo')

if 'foo' in get_methods(B):
    print('B has foo')

0

Mặc dù việc kiểm tra các thuộc tính trong thuộc tính __dict__ rất nhanh, bạn không thể sử dụng thuộc tính này cho các phương thức, vì chúng không xuất hiện trong hàm băm __dict__. Tuy nhiên, bạn có thể dùng đến cách giải quyết vấn đề trong lớp, nếu hiệu suất là rất quan trọng:

class Test():
    def __init__():
        # redefine your method as attribute
        self.custom_method = self.custom_method

    def custom_method(self):
        pass

Sau đó kiểm tra phương thức như:

t = Test()
'custom_method' in t.__dict__

So sánh thời gian với getattr:

>>%timeit 'custom_method' in t.__dict__
55.9 ns ± 0.626 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'custom_method', None)
116 ns ± 0.765 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Không phải tôi đang khuyến khích phương pháp này, nhưng nó có vẻ hiệu quả.

[EDIT] Hiệu suất tăng thậm chí còn cao hơn khi tên phương thức không có trong lớp đã cho:

>>%timeit 'rubbish' in t.__dict__
65.5 ns ± 11 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'rubbish', None)
385 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

1
__dict__có thể bị ghi đè. Nó không thể được tin tưởng.
Xiao
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.