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


132

ฉันกำลังเขียนอัลกอริทึมการค้นหาสถานะ AI และฉันมีคลาสทั่วไปที่สามารถใช้เพื่อใช้อัลกอริทึมการค้นหาได้อย่างรวดเร็ว คลาสย่อยจะกำหนดการดำเนินการที่จำเป็นและอัลกอริทึมจะจัดการส่วนที่เหลือ

ที่นี่ฉันติดขัด: ฉันต้องการหลีกเลี่ยงการสร้างสถานะแม่ซ้ำแล้วซ้ำอีกดังนั้นฉันจึงมีฟังก์ชันต่อไปนี้ซึ่งส่งคืนการดำเนินการที่สามารถนำไปใช้กับรัฐใด ๆ ได้ตามกฎหมาย:

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

และฟังก์ชัน invert_op จะพ่นตามค่าเริ่มต้น

มีวิธีที่เร็วกว่าในการตรวจสอบเพื่อดูว่าฟังก์ชันไม่ได้กำหนดไว้มากกว่าการจับข้อยกเว้นหรือไม่?

ฉันกำลังคิดบางอย่างเกี่ยวกับการตรวจสอบของขวัญใน dir แต่ดูเหมือนจะไม่ถูกต้อง hasattr ถูกใช้งานโดยเรียก getattr และตรวจสอบว่ามันเพิ่มขึ้นหรือไม่ซึ่งไม่ใช่สิ่งที่ฉันต้องการ


8
"hasattr ใช้งานได้โดยเรียก getattr และตรวจสอบว่ามันเพิ่มขึ้นซึ่งไม่ใช่สิ่งที่ฉันต้องการ" ทำไมจะไม่ล่ะ? ทำไมคุณถึงสนใจว่าการนำไปใช้งานทำอะไร?
ย้อนกลับ

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

1
ลอง: hasattr(connection, 'invert_opt').
kenorb

คำตอบ:


205

ใช่ใช้getattr()เพื่อรับแอตทริบิวต์และcallable()เพื่อตรวจสอบว่าเป็นวิธีการ:

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

โปรดทราบว่าgetattr()โดยปกติจะแสดงข้อยกเว้นเมื่อไม่มีแอตทริบิวต์ อย่างไรก็ตามหากคุณระบุค่าเริ่มต้น ( Noneในกรณีนี้) ค่านั้นจะส่งกลับค่านั้นแทน


3
โปรดทราบว่าการใช้งานgetattrในกรณีนี้จะจับข้อยกเว้นโดยไม่โต้ตอบและส่งคืนค่าเริ่มต้นแทนเช่นเดียวกับhasattrที่ OP มีเหตุผลบางประการ
ซานตา

3
จะเกิดอะไรขึ้นถ้าฟังก์ชันไม่ได้อยู่ในคลาสนั้น แต่อยู่ในคลาสพาเรนต์? ในกรณีนี้ฉันได้รับ True แม้ว่าเด็ก ๆ จะไม่เคยใช้ฟังก์ชันนั้น (ใช้ hasattr)
darkgaze

46

ใช้งานได้ทั้ง Python 2 และ Python 3

hasattr(connection, 'invert_opt')

hasattrส่งคืนTrueหากวัตถุการเชื่อมต่อมีการinvert_optกำหนดฟังก์ชัน นี่คือเอกสารสำหรับคุณในการกินหญ้า

https://docs.python.org/2/library/functions.html#hasattr https://docs.python.org/3/library/functions.html#hasattr


5
แม้ว่าโค้ดจะได้รับการชื่นชม แต่ก็ควรมีคำอธิบายประกอบเสมอ ไม่ต้องนาน แต่คาดว่า
peterh - คืนสถานะ Monica

ดีคุณสามารถชี้ไปที่บทความแม้ว่ามันจะไม่เจ็บ :)
Vitaliy Terziev

5
นอกจากนี้ยังส่งกลับค่า True connection.invert_opt = 'foo'หากการเชื่อมต่อมีแอตทริบิวต์
Robert Hönig

20

มีวิธีที่เร็วกว่าในการตรวจสอบเพื่อดูว่าฟังก์ชันไม่ได้กำหนดไว้มากกว่าการจับข้อยกเว้นหรือไม่?

ทำไมคุณถึงต่อต้านสิ่งนั้น? ในกรณี Pythonic ส่วนใหญ่ควรขอการให้อภัยดีกว่าการอนุญาต ;-)

hasattr ถูกใช้งานโดยเรียก getattr และตรวจสอบว่ามันเพิ่มขึ้นหรือไม่ซึ่งไม่ใช่สิ่งที่ฉันต้องการ

อีกครั้งทำไมถึงเป็นเช่นนั้น? สิ่งต่อไปนี้ค่อนข้าง Pythonic:

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

หรือ,

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

อย่างไรก็ตามโปรดทราบว่าgetattr(obj, attr, default)โดยทั่วไปแล้วจะถูกนำไปใช้โดยการจับข้อยกเว้นด้วย ไม่มีอะไรผิดปกติในดินแดน Python!


4

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

วิธีที่เร็วที่สุดในการตรวจสอบว่าวัตถุ obj มีแอตทริบิวต์แอตทริบิวต์คืออะไร คำตอบคือ

'attrib' in obj.__dict__

นี่เป็นเช่นนั้นเนื่องจากการบงการแฮชคีย์ดังนั้นการตรวจสอบการมีอยู่ของคีย์จึงทำได้อย่างรวดเร็ว

ดูการเปรียบเทียบเวลาด้านล่าง

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

__slots__นี้ล้มเหลวในการเรียนการใช้งานที่ __slots__ช่วยเพิ่มความเร็วในการเข้าถึงแอตทริบิวต์ ~ 10% stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ

3

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

นี่คือสูตรสำหรับมัณฑนากรที่เรียกว่า "lru_cache" โดย Raymond Hettinger ตอนนี้เวอร์ชันนี้เป็นมาตรฐานในโมดูล functools ใน Python 3.2

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

http://docs.python.org/release/3.2/library/functools.html


2

เช่นเดียวกับสิ่งใด ๆ ใน Python ถ้าคุณพยายามมากพอคุณจะกล้าและทำสิ่งที่น่ารังเกียจจริงๆ ตอนนี้นี่คือส่วนที่น่ารังเกียจ:

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'

โปรดช่วยเราด้วยเพียงแค่ทำในสิ่งที่คุณมีคำถามและอย่าใช้สิ่งนี้เว้นแต่คุณจะอยู่ในทีม PyPy ที่แฮ็กเข้าสู่ล่าม Python สิ่งที่คุณต้องไปอยู่ที่นั่นเป็น Pythonic สิ่งที่ฉันมีที่นี่เป็นที่บริสุทธิ์ชั่วร้าย


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

1

คุณยังสามารถข้ามชั้นเรียนได้:

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

ในขณะที่การตรวจสอบแอตทริบิวต์ในคุณสมบัติ __dict__ นั้นเร็วมาก แต่คุณไม่สามารถใช้วิธีนี้กับเมธอดได้เนื่องจากไม่ปรากฏในแฮช __dict__ อย่างไรก็ตามคุณสามารถใช้วิธีแก้ปัญหาแฮ็กในชั้นเรียนของคุณได้หากประสิทธิภาพเป็นสิ่งสำคัญ:

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

    def custom_method(self):
        pass

จากนั้นตรวจสอบวิธีการเป็น:

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

การเปรียบเทียบเวลากับ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)

ไม่ใช่ว่าฉันสนับสนุนแนวทางนี้ แต่ดูเหมือนว่าจะได้ผล

[แก้ไข] การเพิ่มประสิทธิภาพจะยิ่งสูงขึ้นเมื่อชื่อเมธอดไม่อยู่ในคลาสที่กำหนด:

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