Python: Liên kết một phương thức không giới hạn?


117

Trong Python, có cách nào để liên kết một phương thức không liên kết mà không gọi nó không?

Tôi đang viết một chương trình wxPython và đối với một lớp nhất định, tôi đã quyết định sẽ rất tốt khi nhóm dữ liệu của tất cả các nút của mình thành một danh sách các bộ dữ liệu cấp độ, như vậy:

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)

Vấn đề là, vì tất cả các giá trị của handlercác phương thức không liên kết, chương trình của tôi bùng nổ trong một ngọn lửa ngoạn mục và tôi khóc.

Tôi đã tìm kiếm trên mạng một giải pháp cho những gì có vẻ như là một vấn đề tương đối đơn giản, có thể giải quyết được. Thật không may, tôi không thể tìm thấy bất cứ điều gì. Ngay bây giờ, tôi đang sử dụng functools.partialđể giải quyết vấn đề này, nhưng có ai biết liệu có một cách Pythonic sạch sẽ, lành mạnh để liên kết một phương thức không liên kết với một thể hiện và tiếp tục truyền qua nó mà không gọi nó không?


6
@Christopher - Một phương thức không bị ràng buộc với phạm vi của đối tượng mà nó bị hút từ đó, vì vậy bạn phải tự vượt qua một cách rõ ràng.
Aiden Bell

2
Tôi đặc biệt thích "ngọn lửa ngoạn mục và tôi khóc."
jspencer

Câu trả lời:


174

Tất cả các hàm cũng là mô tả , vì vậy bạn có thể liên kết chúng bằng cách gọi __get__phương thức của chúng :

bound_handler = handler.__get__(self, MyWidget)

Đây là hướng dẫn tuyệt vời của R. Hettinger để mô tả.


Như một ví dụ khép kín lấy từ nhận xét của 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
Điều đó thật tuyệt. Tôi thích cách bạn có thể bỏ qua loại và lấy lại "phương thức bị ràng buộc? .F".
Kiv

Tôi thích giải pháp này hơn MethodTypemột, vì nó hoạt động tương tự trong py3k, trong khi MethodTypecác đối số đã được thay đổi một chút.
bgw

10
Và do đó, một hàm để liên kết các hàm với các thể hiện của lớp: bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))Ví dụ:class A: pass; a = A(); bind(a, bind, 'bind')
Keith Pinson

2
Huh, bạn học được điều gì đó mới mỗi ngày. @Kazark Trong Python 3, ít nhất, bạn cũng có thể bỏ qua việc cung cấp loại, vì __get__sẽ lấy nó hoàn toàn từ tham số đối tượng. Tôi thậm chí không chắc việc cung cấp nó có làm gì không, vì nó không khác biệt loại tôi cung cấp làm tham số thứ hai bất kể tham số đầu tiên là gì. Vì vậy, bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))nên làm các thủ thuật là tốt. (Mặc dù tôi muốn có bindthể sử dụng như một người trang trí, cá nhân, nhưng đó là một vấn đề khác.)
JAB

1
Wow, không bao giờ biết chức năng là mô tả. Đó là một thiết kế rất thanh lịch, các phương thức chỉ là các hàm đơn giản trong lớp ' __dict__và truy cập thuộc tính cung cấp cho bạn các phương thức không bị ràng buộc hoặc bị ràng buộc thông qua giao thức mô tả thông thường. Tôi luôn cho rằng đó là một loại phép thuật đã xảy ra trong thời giantype.__new__()
JaredL

81

Điều này có thể được thực hiện sạch sẽ với các loại.MethodType . Thí dụ:

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 Điều này thật tuyệt vời, nhưng không có tài liệu tham khảo nào trong tài liệu python tại URL bạn cung cấp.
Kyle Wild

6
+1, tôi không muốn có các lệnh gọi đến các hàm ma thuật trong mã của mình (tức là __get__). Tôi không biết phiên bản nào của python mà bạn đã thử nghiệm này, nhưng trên python 3.4, MethodTypehàm này có hai đối số. Các chức năng và ví dụ. Vì vậy, điều này nên được thay đổi thành types.MethodType(f, C()).
Dan Milon

Nó đây rồi! Đó là một cách tốt để vá các phương thức cá thể:wgt.flush = types.MethodType(lambda self: None, wgt)
Winand

2
Nó thực sự được đề cập trong các tài liệu, nhưng trong trang mô tả từ câu trả lời khác: docs.python.org/3/howto/descriptor.html#fifts-and-methods
kai

9

Tạo một bao đóng với chính nó trong kỹ thuật sẽ không ràng buộc chức năng, nhưng nó là một cách khác để giải quyết vấn đề cơ bản tương tự (hoặc rất giống nhau). Đây là một ví dụ tầm thường:

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

1
Vâng, đây là giống như sửa chữa ban đầu của tôi, được sử dụngfunctools.partial(handler, self)
Dan Passaro

7

Điều này sẽ liên kết selfvới handler:

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

Điều này hoạt động bằng cách chuyển selfnhư là đối số đầu tiên cho hàm. object.function()chỉ là cú pháp đường cho function(object).


1
Có, nhưng điều này gọi phương thức. Vấn đề là tôi cần có khả năng vượt qua phương thức bị ràng buộc như một đối tượng có thể gọi được. Tôi có phương thức không liên kết và ví dụ tôi muốn nó bị ràng buộc, nhưng không thể tìm ra cách kết hợp tất cả lại với nhau mà không gọi ngay lập tức
Dan Passaro

9
Không, không, nó sẽ chỉ gọi phương thức nếu bạn thực hiện ràng buộc (). Xác định lambda không gọi lambda.
brian-brazil

1
Bạn thực sự có thể sử dụng functools.partialthay vì định nghĩa lambda. Nó không giải quyết vấn đề chính xác, mặc dù. Bạn vẫn đang làm việc với một functionthay vì một instancemethod.
Alan Plum

5
@Alan: sự khác biệt giữa một functionđối số đầu tiên mà bạn tranh luận một phần và instancemethod; gõ vịt không thể thấy sự khác biệt.
Lie Ryan

2
@LieRyan sự khác biệt là bạn vẫn chưa xử lý loại cơ bản. functools.partialgiảm một số siêu dữ liệu, ví dụ __module__. (Ngoài ra tôi muốn tuyên bố về hồ sơ mà tôi rất khó chịu khi nhìn vào nhận xét đầu tiên của mình về câu trả lời này.) Thực tế trong câu hỏi của tôi, tôi đã đề cập đến việc tôi đã sử dụng functools.partialnhưng tôi cảm thấy như phải có một cách "tinh khiết" hơn, vì nó dễ dàng để có được cả hai phương thức không ràng buộc và ràng buộc.
Dan Passaro

1

Đến bữa tiệc muộn, nhưng tôi đến đây với một câu hỏi tương tự: Tôi có một phương thức lớp và một thể hiện và muốn áp dụng thể hiện cho phương thức đó.

Có nguy cơ quá đơn giản hóa câu hỏi của OP, cuối cùng tôi đã làm một điều gì đó ít bí ẩn hơn có thể hữu ích cho những người khác đến đây (báo trước: Tôi đang làm việc trong Python 3 - YMMV).

Hãy xem xét lớp đơn giản này:

class Foo(object):

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

    def value(self):
        return self._value

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

Đây là những gì bạn có thể làm với nó:

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

Điều này không giải quyết được vấn đề của tôi - đó là tôi muốn methtrở thành bất khả xâm phạm mà không phải gửi cho nó ađối số (đó là lý do tại sao tôi sử dụng ban đầu functools.partial) - nhưng điều này tốt hơn nếu bạn không cần truyền phương thức xung quanh và chỉ có thể gọi nó ngay tại chỗ. Ngoài ra, điều này hoạt động theo cách tương tự trong Python 2 giống như trong Python 3.
Dan Passaro

Xin lỗi vì không đọc yêu cầu ban đầu của bạn cẩn thận hơn. Tôi là một phần (ý định chơi chữ) đối với cách tiếp cận dựa trên lambda được đưa ra bởi @ brian-brazil trong stackoverflow.com/a/1015355/558639 - nó hoàn toàn thuần khiết như bạn có thể nhận được.
sợ
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.