2011-06-25 19 views
21

Na podstawie mojego zrozumienia Python's data model, a szczególnie podsekcji "Metody instancji", za każdym razem, gdy czytasz atrybut, którego wartość jest typu "funkcja zdefiniowana przez użytkownika", niektóre magiczne kopnięcia i otrzymujesz metoda związanej instancji zamiast faktycznej, oryginalnej funkcji. Ta magia powoduje, że podczas wywoływania metody nie przekazujesz jawnie parametru self.Przypisywanie funkcji do atrybutu obiektu

Ale potem, będę oczekiwać, aby móc zastąpić metodę wykonania obiektu z funkcji z tym samym podpisem:

class Scriptable: 
    def __init__(self, script = None): 
     if script is not None: 
      self.script = script # replace the method 
    def script(self): 
     print("greetings from the default script") 

>>> scriptable = Scriptable() 
>>> scriptable.script() 
greetings from the default script 

>>> def my_script(self): 
...  print("greetings from my custom script") 
... 
>>> scriptable = Scriptable(my_script) 
>>> scriptable.script() 
Traceback (most recent call last): 
    ... 
TypeError: script() takes exactly 1 positional argument (0 given) 

tworzę instancję Scriptable i ustawienie jego atrybut script użytkownikowi -definiowana funkcja z pojedynczym parametrem, podobnie jak zdefiniowano w klasie. Więc kiedy przeczytałem atrybut scriptable.script, oczekiwałbym, że magia się pojawi i poda mi metodę związaną z instancją, która nie przyjmuje parametrów (tak jak ja, kiedy nie zastąpiłem script). Zamiast tego wydaje się, że oddaje dokładnie tę samą funkcję, którą przekazałem, parametr self i wszystko. Magia wiążąca metody nie dzieje się.

Dlaczego magia wiążąca metody działa, gdy definiuję metodę wewnątrz deklaracji klasy, ale nie wtedy, gdy przypisuję atrybut? Co sprawia, że ​​Python traktuje te sytuacje inaczej?

Używam Python3, jeśli robi różnicę.

+2

Podobnie jak w przypadku większości magii, został zaprojektowany, aby zrobić dokładnie tak, jakbym się tego prawa: „Ważne jest również, aby pamiętać, że funkcje zdefiniowane przez użytkownika które są ** atrybutami instancji klasy ** są ** nie ** konwertowane na metody związane, a dzieje się tak tylko wtedy, gdy funkcja jest atrybutem ** klasy **. " Za pomocą instrukcji "self.script" tworzysz atrybut instancji. Statyczna metoda "Scriptable.script" nadal istnieje. Możliwym obejściem byłoby wywołanie twojego wstrzykniętego 'script' w poprzedniej metodzie' script'. –

Odpowiedz

13

Oto jak cię zrobić:

import types 
class Scriptable: 
    def __init__(self, script = None): 
     if script is not None: 
      self.script = types.MethodType(script, self) # replace the method 
    def script(self): 
     print("greetings from the default script") 

Jak ba__friend zauważył w komentarzach, metody są przechowywane w obiekcie class. Deskryptor na obiekcie klasy zwraca funkcje jako powiązane metody, gdy uzyskujesz dostęp do atrybutu z instancji.

Po przypisaniu funkcji do instance nic się nie dzieje, dzieje się tak dlatego, że trzeba samemu owijać tę funkcję.

+0

Fajnie! Po przeczytaniu komentarza ba__frienda, stwierdziłem, że mogę zrobić "self.script = lambda: script (self)", aby uzyskać pożądane wyniki, ale to jest zakodowane dla metody bez parametrów. Twoja jest o wiele lepsza. –

+0

Lepiej sprawdź, czy 'script' jest dostępny tylko po to, aby upewnić się, że: – kentwait

6

Spójrz na to:

>>> scriptable = Scriptable() 
>>> scriptable.script 
<bound method Scriptable.script of <__main__.Scriptable instance at 0x01209DA0>> 
>>> scriptable = Scriptable(my_script) 
>>> scriptable.script 
<function my_script at 0x00CF9730> 

komunikat self.script = script tworzy tylko atrybut obiektu klasy, bez żadnej „magii” z nim.

Instrukcja def script(self): w definicji klasy tworzy deskryptor - specjalny obiekt, który w rzeczywistości zarządza wszystkimi elementami za pomocą parametru self.

Możesz przeczytać więcej o deskryptorach w Pythonie we wspomnianym modelu danych: implementing-descriptors.

Jeszcze jeden świetny artykuł na temat deskryptorów w języku Python od Raymonda Hettingera: How-To Guide for Descriptors.

+0

To również bardzo proste przejście, które robi dokładnie to, o co prosił OP: http://irrepupavel.com/documents/python/instancemethod/ (najlepsze trafienie w Google dla "python create bound method"). –

+0

Istnieje podobna magia, ale nie sądzę, że 'def script (self):' faktycznie tworzy deskryptor. Jeśli robię 'Scriptable.script = my_script', to wszystko działa zgodnie z oczekiwaniami i nie ma tam tworzonego deskryptora. Wygląda na to, że komentarz ba__friend jest poprawny: magia działa tylko dla atrybutów * typu *, a nie atrybutów * instancji *. –

+0

Dobra, poprawiam się. Po zagraniu z kodem warvariuc (http://stackoverflow.com/questions/6478371/assigning-a-function-to-an-object-attribute/6479888#6479888), jasne jest, że funkcje * to * deskryptory - automatycznie Atrybuty "__get__", nawet jeśli nie widzę tego udokumentowanego nigdzie. Myślałem, że 'type .__ getattr__' ma specjalny kod dla funkcji, ale wygląda na to, że tak naprawdę traktuje je jak każdy inny deskryptor. –

1

nie mogę odpowiedzieć na to pytanie dlaczego to działa tak, będziesz musiał poprosić Guido van Rossum, ale mogę dać ci ewentualnego obejścia:

class Scriptable: 
    def __init__(self, script = None): 
     self._script = script # replace the method 
    def script(self): 
     if self._script: return self._script(self) 
     return self._defaultscript() 
    def _defaultscript(self): 
     print("greetings from the default script") 
9

Dzięki Alex Martelli na answer tutaj jest inna wersja:

class Scriptable: 
    def script(self): 
     print(self) 
     print("greetings from the default script") 

def another_script(self): 
    print(self) 
    print("greetings from the another script") 

s = Scriptable() 
s.script() 

# monkey patching: 
s.script = another_script.__get__(s, Scriptable) 
s.script() 
+3

Crazy. Nie miałem pojęcia, że ​​funkcje automatycznie mają atrybuty "__get__" - dokumentacja nawet nie wspomina o tym, że robią - ale działając na twoim przykładzie, widzę, że to robią. Podobnie jak "lambda". Więc każda funkcja może być automatycznie używana jako deskryptor ... Python nadal mnie zadziwia. Rzeczy, które uważałem za specjalne, okazują się ogólne. –

Powiązane problemy