Python: ผูกวิธีหลุด?


118

ใน Python มีวิธีการผูก unbound method โดยไม่ต้องเรียกมันหรือไม่?

ฉันกำลังเขียนโปรแกรม wxPython และสำหรับบางคลาสฉันคิดว่ามันเป็นการดีที่จะจัดกลุ่มข้อมูลของปุ่มทั้งหมดของฉันเข้าด้วยกันเป็นรายการทูเปิลระดับคลาสเช่น:

class MyWidget(wx.Window):
    buttons = [("OK", OnOK),
               ("Cancel", OnCancel)]

    # ...

    def Setup(self):
        for text, handler in MyWidget.buttons:

            # This following line is the problem line.
            b = wx.Button(parent, label=text).Bind(wx.EVT_BUTTON, handler)

ปัญหาคือเนื่องจากค่าทั้งหมดhandlerเป็นวิธีการที่ไม่ถูกผูกไว้โปรแกรมของฉันจึงระเบิดออกมาเป็นเปลวไฟที่งดงามและฉันก็ร้องไห้

ฉันมองหาวิธีแก้ปัญหาทางออนไลน์เกี่ยวกับสิ่งที่ดูเหมือนว่าควรจะเป็นปัญหาที่ค่อนข้างตรงไปตรงมาและแก้ไขได้ น่าเสียดายที่ไม่พบอะไรเลย ตอนนี้ฉันกำลังใช้functools.partialเพื่อแก้ไขปัญหานี้ แต่มีใครรู้บ้างว่ามีวิธี Pythonic ที่สะอาดและมีสุขภาพดีในการผูกวิธีที่ไม่ถูกผูกไว้กับอินสแตนซ์และดำเนินการต่อโดยไม่เรียกมัน


6
@ คริสโตเฟอร์ - วิธีการที่ไม่ผูกมัดกับขอบเขตของวัตถุที่มันถูกดูดดังนั้นคุณต้องส่งผ่านตัวเองอย่างชัดเจน
Aiden Bell

2
ฉันชอบ "เปลวไฟอันตระการตาและฉันร้องไห้" เป็นพิเศษ
jspencer

คำตอบ:


175

ฟังก์ชั่นทั้งหมดยังเป็นตัวบอกดังนั้นคุณสามารถผูกได้โดยเรียก__get__ใช้ method:

bound_handler = handler.__get__(self, MyWidget)

นี่คือคำแนะนำที่ยอดเยี่ยมของ R.Hettinger สำหรับตัวอธิบาย


ตามตัวอย่างที่มีอยู่ในตัวดึงมาจากความคิดเห็นของ Keith :

def bind(instance, func, as_name=None):
    """
    Bind the function *func* to *instance*, with either provided name *as_name*
    or the existing name of *func*. The provided *func* should accept the 
    instance as the first argument, i.e. "self".
    """
    if as_name is None:
        as_name = func.__name__
    bound_method = func.__get__(instance, instance.__class__)
    setattr(instance, as_name, bound_method)
    return bound_method

class Thing:
    def __init__(self, val):
        self.val = val

something = Thing(21)

def double(self):
    return 2 * self.val

bind(something, double)
something.double()  # returns 42

3
มันเจ๋งมาก ฉันชอบวิธีที่คุณสามารถละประเภทและรับ "วิธีการผูกมัด? .f" แทน
Kiv

ฉันชอบวิธีแก้ปัญหานี้มากกว่าMethodTypeเพราะมันใช้งานได้เหมือนกันใน py3k ในขณะที่MethodTypeอาร์กิวเมนต์มีการเปลี่ยนแปลงเล็กน้อย
bgw

10
ดังนั้นฟังก์ชันในการผูกฟังก์ชันกับอินสแตนซ์คลาส: bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))ตัวอย่าง:class A: pass; a = A(); bind(a, bind, 'bind')
Keith Pinson

2
หึคุณเรียนรู้สิ่งใหม่ ๆ ทุกวัน @Kazark ใน Python 3 อย่างน้อยคุณยังสามารถข้ามการจัดหาประเภทได้เนื่องจาก__get__จะใช้โดยปริยายจากพารามิเตอร์วัตถุ ฉันไม่แน่ใจด้วยซ้ำว่าการจัดหามันทำอะไรเพราะมันไม่ได้สร้างความแตกต่างว่าประเภทใดที่ฉันจัดหาเป็นพารามิเตอร์ที่สองไม่ว่าพารามิเตอร์ตัวแรกจะเป็นอินสแตนซ์ของอะไรก็ตาม ดังนั้นbind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))ควรทำเคล็ดลับด้วย (แม้ว่าฉันจะต้องการbindใช้งานในฐานะมัณฑนากรเป็นการส่วนตัว แต่ก็เป็นเรื่องที่แตกต่างออกไป)
JAB

1
ว้าวไม่เคยรู้เลยว่าฟังก์ชันเป็นตัวบ่งชี้ นั่นเป็นการออกแบบที่สวยงามมากวิธีการเป็นเพียงฟังก์ชันธรรมดาในคลาส ' __dict__และการเข้าถึงแอตทริบิวต์ช่วยให้คุณไม่ถูกผูกมัดหรือถูกผูกไว้ผ่านโปรโตคอลตัวบอกปกติ ฉันคิดเสมอว่ามันเป็นเวทมนตร์บางอย่างที่เกิดขึ้นระหว่างtype.__new__()
JaredL

81

ซึ่งสามารถทำได้อย่างหมดจดด้วยtypes.MethodType ตัวอย่าง:

import types

def f(self): print self

class C(object): pass

meth = types.MethodType(f, C(), C) # Bind f to an instance of C
print meth # prints <bound method C.f of <__main__.C object at 0x01255E90>>

10
+1 สิ่งนี้ยอดเยี่ยม แต่ไม่มีการอ้างอิงถึงในเอกสาร python ที่ URL ที่คุณระบุ
Kyle Wild

7
+1 ฉันไม่ต้องการให้มีการเรียกใช้ฟังก์ชันเวทย์มนตร์ในรหัสของฉัน (เช่น__get__) ฉันไม่ทราบว่าคุณทดสอบ Python เวอร์ชันใด แต่ใน python 3.4 MethodTypeฟังก์ชันนี้ใช้อาร์กิวเมนต์สองตัว ฟังก์ชันและอินสแตนซ์ types.MethodType(f, C())ดังนั้นนี้ควรจะเปลี่ยนไป
Dan Milon

นี่ไง! เป็นวิธีที่ดีในการแก้ไขวิธีการอินสแตนซ์:wgt.flush = types.MethodType(lambda self: None, wgt)
Winand

2
มีการกล่าวถึงจริงในเอกสาร แต่ในหน้า descriptor จากคำตอบอื่น: docs.python.org/3/howto/descriptor.html#functions-and-methods
ไก่

9

การสร้างการปิดด้วยตัวเองจะไม่ผูกมัดฟังก์ชันในทางเทคนิค แต่เป็นอีกทางเลือกหนึ่งในการแก้ปัญหาพื้นฐานเดียวกัน (หรือคล้ายกันมาก) นี่คือตัวอย่างเล็กน้อย:

self.method = (lambda self: lambda args: self.do(args))(self)

1
ใช่นี่เป็นเรื่องเดียวกับการแก้ไขเดิมของฉันซึ่งจะใช้functools.partial(handler, self)
Dan Passaro

7

สิ่งนี้จะผูกselfกับhandler:

bound_handler = lambda *args, **kwargs: handler(self, *args, **kwargs)

สิ่งนี้ใช้ได้โดยส่งselfเป็นอาร์กิวเมนต์แรกไปยังฟังก์ชัน เป็นเพียงน้ำตาลประโยคสำหรับobject.function()function(object)


1
ใช่ แต่สิ่งนี้เรียกวิธีการ ปัญหาคือฉันต้องสามารถส่งผ่านวิธีการผูกเป็นวัตถุที่เรียกได้ ฉันมีวิธียกเลิกการผูกมัดและอินสแตนซ์ที่ฉันต้องการจะผูกไว้ แต่ไม่สามารถหาวิธีรวมทั้งหมดเข้าด้วยกันโดยไม่ต้องเรียกมันทันที
Dan Passaro

9
ไม่มันจะเรียกเมธอดก็ต่อเมื่อคุณทำ bound_handler () การกำหนดแลมด้าไม่เรียกแลมด้า
brian-brazil

1
คุณสามารถใช้functools.partialแทนการกำหนดแลมด้าได้ แม้ว่าจะไม่ได้แก้ปัญหาที่แน่นอน คุณยังคงติดต่อกับแทนfunction instancemethod
Alan Plum

5
@Alan: อะไรคือความแตกต่างระหว่างfunctionอาร์กิวเมนต์แรกที่คุณใช้เป็นบางส่วนและinstancemethod; การพิมพ์เป็ดไม่เห็นความแตกต่าง
โกหกไรอัน

2
@LieRyan ความแตกต่างคือคุณยังไม่ได้จัดการกับประเภทพื้นฐาน เมตาดาต้าลดลงบางส่วนเช่นfunctools.partial __module__(และฉันก็อยากจะบอกว่าฉันประจบประแจงอย่างหนักเมื่อดูความคิดเห็นแรกของฉันเกี่ยวกับคำตอบนี้) อันที่จริงในคำถามของฉันฉันพูดถึงว่าฉันใช้อยู่แล้วfunctools.partialแต่ฉันรู้สึกว่าต้องเป็นวิธีที่ "บริสุทธิ์กว่า" เนื่องจากเป็นเรื่องง่ายที่จะได้รับทั้งวิธีที่ไม่ถูกผูกไว้และแบบผูกมัด
Dan Passaro

1

สายไปงานปาร์ตี้ แต่ฉันมาที่นี่พร้อมกับคำถามที่คล้ายกัน: ฉันมีคลาสเมธอดและอินสแตนซ์และต้องการใช้อินสแตนซ์กับเมธอด

ด้วยความเสี่ยงที่จะทำให้คำถามของ OP ชัดเจนเกินไปฉันจึงทำบางสิ่งที่ลึกลับน้อยกว่าซึ่งอาจเป็นประโยชน์กับคนอื่น ๆ ที่มาถึงที่นี่ (ข้อแม้: ฉันทำงานใน Python 3 - YMMV)

พิจารณาคลาสง่ายๆนี้:

class Foo(object):

    def __init__(self, value):
        self._value = value

    def value(self):
        return self._value

    def set_value(self, value):
        self._value = value

นี่คือสิ่งที่คุณสามารถทำได้:

>>> meth = Foo.set_value   # the method
>>> a = Foo(12)            # a is an instance with value 12
>>> meth(a, 33)            # apply instance and method
>>> a.value()              # voila - the method was called
33

สิ่งนี้ไม่ช่วยแก้ปัญหาของฉัน - ซึ่งก็คือฉันต้องการที่methจะเรียกใช้โดยไม่ต้องส่งaอาร์กิวเมนต์ (ซึ่งเป็นเหตุผลที่ฉันใช้ในตอนแรกfunctools.partial) - แต่จะดีกว่าถ้าคุณไม่จำเป็นต้องผ่านวิธีการรอบ ๆ และสามารถทำได้ เรียกมันทันที นอกจากนี้ยังใช้งานได้ในลักษณะเดียวกันกับ Python 2 เช่นเดียวกับใน Python 3
Dan Passaro

ขออภัยที่ไม่ได้อ่านข้อกำหนดเดิมของคุณอย่างรอบคอบมากขึ้น ฉันเป็นบางส่วน (ตั้งใจเล่นสำนวน) กับวิธีการที่ใช้แลมบ์ดาที่กำหนดโดย @ brian-brazil ในstackoverflow.com/a/1015355/558639 - มันเป็นเรื่องที่บริสุทธิ์ที่สุดเท่าที่คุณจะทำได้
fearless_fool
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.