Python: Sınırsız Bir Yöntemi Bağlamak?


117

Python'da, bağlanmamış bir yöntemi çağırmadan bağlamanın bir yolu var mı?

Bir wxPython programı yazıyorum ve belirli bir sınıf için tüm düğmelerimin verilerini sınıf düzeyinde bir tuple listesi olarak gruplamanın güzel olacağına karar verdim, şöyle:

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)

Sorun şu ki, tüm değerleri handlerbağlı olmayan yöntemler olduğundan, programım muhteşem bir alevle patlıyor ve ağlıyorum.

Görece basit, çözülebilir bir problem gibi görünen bir şeye bir çözüm bulmak için internette etrafa bakıyordum. Maalesef hiçbir şey bulamadım. Şu anda, bunu aşmak için kullanıyorum functools.partial, ancak bağlanmamış bir yöntemi bir örneğe bağlamanın ve onu aramadan geçirmeye devam etmenin temiz, sağlıklı, Pythonic bir yolu olup olmadığını bilen var mı?


6
@Christopher - Çekildiği nesnenin kapsamına bağlı olmayan bir yöntemdir, bu nedenle kendini açıkça iletmeniz gerekir.
Aiden Bell

2
Özellikle "muhteşem alev ve ağlarım" ı seviyorum.
jspencer

Yanıtlar:


174

Tüm işlevler aynı zamanda tanımlayıcıdır , bu nedenle __get__yöntemlerini çağırarak bunları bağlayabilirsiniz :

bound_handler = handler.__get__(self, MyWidget)

İşte R. Hettinger'in mükemmel tanımlayıcı rehberi .


Keith'in yorumundan bağımsız bir örnek olarak :

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
Bu oldukça havalı. Türü atlayıp bunun yerine bir "bağlı yöntem? .F" nasıl geri alabileceğinizi seviyorum.
Kiv

Bu çözümü MethodTypebirden çok seviyorum , çünkü py3k'de aynı şekilde çalışıyor, ancak MethodTypeargümanları biraz değiştirildi.
bgw

10
Ve böylece, sınıf örneklerine işlevleri bağlamak için bir işlev: bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__))Örnek:class A: pass; a = A(); bind(a, bind, 'bind')
Keith Pinson

2
Huh, her gün yeni bir şey öğreniyorsun. @Kazark Python 3'te, en azından, __get__nesne parametresinden örtük olarak alacağı gibi, türü sağlamayı da atlayabilirsiniz . İlk parametrenin bir örneği ne olursa olsun, ikinci parametre olarak hangi türü sağladığımın hiçbir önemi olmadığı için, tedarik etmenin bir şey yapıp yapmadığından bile emin değilim. Yani bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance))hem hile yapmak gerekir. ( bindKişisel olarak bir dekoratör olarak kullanılmayı tercih etsem de, bu farklı bir mesele.)
JAB

1
Vay canına, işlevlerin tanımlayıcı olduğunu hiç bilmiyordum. Bu çok zarif bir tasarım, yöntemler yalnızca sınıfta basit işlevlerdir __dict__ve öznitelik erişimi, normal tanımlayıcı protokolü aracılığıyla size sınırsız veya bağlı yöntemler sağlar. Her zaman bunun bir tür sihir olduğunu type.__new__()
düşünmüşümdür

81

Bu, types.MethodType ile temiz bir şekilde yapılabilir . Misal:

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 Bu harika, ancak verdiğiniz URL'deki python belgelerinde buna referans yok.
Kyle Wild

7
+1, kodumda (yani __get__) sihirli işlevlere çağrı yapılmamasını tercih ederim . Bunu hangi python sürümü için test ettiğinizi bilmiyorum, ancak python 3.4'te MethodTypeişlev iki argüman alıyor. İşlev ve örnek. Yani bu olarak değiştirilmelidir types.MethodType(f, C()).
Dan Milon

İşte burada! Örnek yöntemlerini wgt.flush = types.MethodType(lambda self: None, wgt)
yamamanın

2
Aslında belgelerde bahsediliyor, ancak diğer yanıtın tanımlayıcı sayfasında: docs.python.org/3/howto/descriptor.html#functions-and-methods
kai

9

İçinde öz olan bir kapanış yaratmak, işlevi teknik olarak bağlamaz, ancak aynı (veya çok benzer) temeldeki sorunu çözmenin alternatif bir yoludur. İşte önemsiz bir örnek:

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

1
Evet, bu, kullanılacak olan orijinal düzeltmemle hemen hemen aynıfunctools.partial(handler, self)
Dan Passaro

7

Bu bağlayacaktır selfiçin handler:

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

Bu self, işleve ilk argüman olarak iletilerek çalışır. object.function()sadece sözdizimsel şekerdir function(object).


1
Evet, ama bu yöntemi çağırıyor. Sorun şu ki, bağlı yöntemi çağrılabilir bir nesne olarak geçirebilmem gerekiyor. Bağlantısız yönteme ve bağlı olmasını istediğim örneğe sahibim, ancak hemen çağırmadan hepsini nasıl bir araya
getireceğimi çözemiyorum

9
Hayır değil, yalnızca bound_handler () yaparsanız yöntemi çağırır. Bir lambda tanımlamak lambda'yı çağırmaz.
brian-brezilya

1
Aslında functools.partialbir lambda tanımlamak yerine kullanabilirsiniz . Yine de kesin sorunu çözmez. Hala bir functionyerine a ile uğraşıyorsunuz instancemethod.
Alan Plum

5
@Alan: functionİlk argümanını kısmi yaptığınız a ile instancemethod; ördek yazarak farkı göremezsiniz.
Lie Ryan

2
@LieRyan aradaki fark, hala temel türle uğraşmamanızdır. functools.partialbazı meta verileri bırakır, örneğin __module__. (Ayrıca, bu cevaba ilişkin ilk yorumuma baktığımda gerçekten çok üzüldüğümü kayıt için belirtmek istiyorum.) Aslında sorumda zaten kullandığımı functools.partialsöyledim ama "daha saf" bir yol olması gerektiğini hissettim. çünkü hem bağlı olmayan hem de bağlı olmayan yöntemleri elde etmek kolaydır.
Dan Passaro

1

Partiye geç kaldım ama buraya benzer bir soruyla geldim: Bir sınıf yöntemim ve bir örneğim var ve örneği yönteme uygulamak istiyorum.

OP'nin sorusunu aşırı basitleştirme riskiyle, buraya gelenler için faydalı olabilecek daha az gizemli bir şey yaptım (uyarı: Python 3 - YMMV'de çalışıyorum).

Bu basit sınıfı düşünün:

class Foo(object):

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

    def value(self):
        return self._value

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

İşte onunla yapabilecekleriniz:

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

Bu benim sorunumu çözmüyor - argüman methgöndermek zorunda kalmadan çağrılabilir olmak istedim a(bu yüzden başlangıçta kullandım functools.partial) - ancak yöntemi başkalarına iletmeniz gerekmiyorsa ve sadece yerinde çağırın. Ayrıca bu, Python 2'de de Python 3'te olduğu gibi çalışır.
Dan Passaro

Orijinal gereksinimlerinizi daha dikkatli okumadığınız için özür dileriz. @ Brian-brazil tarafından stackoverflow.com/a/1015355/558639'da verilen lambda temelli yaklaşıma kısmi (amaçlanan) bir yaklaşımım var - alabileceğiniz kadar saf.
fearless_fool
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.