การเพิ่มวิธีการไปยังอินสแตนซ์ของวัตถุที่มีอยู่


642

ฉันอ่านว่ามันเป็นไปได้ที่จะเพิ่มวิธีการในวัตถุที่มีอยู่ (เช่นไม่ได้อยู่ในคำจำกัดความของชั้นเรียน) ใน Python

ฉันเข้าใจว่ามันไม่ดีเสมอไป แต่คนเราจะทำสิ่งนี้ได้อย่างไร?

คำตอบ:


921

ใน Python มีความแตกต่างระหว่างฟังก์ชั่นและวิธีการที่ถูกผูกไว้

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

วิธีการที่ถูกผูกไว้ได้รับ "ผูกพัน" (วิธีอธิบาย) กับอินสแตนซ์และอินสแตนซ์นั้นจะถูกส่งผ่านเป็นอาร์กิวเมนต์แรกเมื่อใดก็ตามที่วิธีการที่เรียกว่า

Callables ที่เป็นคุณลักษณะของคลาส (ซึ่งตรงข้ามกับอินสแตนซ์) ยังคงไม่ถูกผูกไว้ดังนั้นคุณสามารถแก้ไขคำจำกัดความของคลาสได้ทุกเมื่อที่คุณต้องการ:

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters

อินสแตนซ์ที่กำหนดไว้ก่อนหน้านี้ได้รับการอัปเดตเช่นกัน (ตราบใดที่ยังไม่ได้เขียนทับแอตทริบิวต์เอง):

>>> a.fooFighters()
fooFighters

ปัญหาเกิดขึ้นเมื่อคุณต้องการแนบวิธีกับอินสแตนซ์เดียว:

>>> def barFighters( self ):
...     print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

ฟังก์ชั่นจะไม่ถูกผูกมัดโดยอัตโนมัติเมื่อเชื่อมต่อกับอินสแตนซ์โดยตรง:

>>> a.barFighters
<function barFighters at 0x00A98EF0>

ในการผูกมันเราสามารถใช้ฟังก์ชัน MethodType ในโมดูล types :

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

เวลานี้อินสแตนซ์อื่นของคลาสไม่ได้รับผลกระทบ:

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'

ข้อมูลเพิ่มเติมสามารถพบได้โดยการอ่านเกี่ยวกับการอธิบายและmetaclass การเขียนโปรแกรม


65
แทนที่จะสร้างด้วยตนเองMethodType, เรียกโปรโตคอลบ่งตนเองและมีฟังก์ชั่นการผลิตเช่นคุณ: barFighters.__get__(a)ผลิตวิธีการที่ถูกผูกไว้สำหรับผูกไว้กับbarFighters a
Martijn Pieters

2
@MartijnPieters ข้อได้เปรียบใด ๆ ของการใช้descriptor protocolvs การสร้างMethodTypeนอกเหนือจากอาจจะอ่านง่ายขึ้นเล็กน้อย
EndermanAPM

17
@EndermanAPM: หลายรายการ: มีแนวโน้มที่จะทำงานต่อไปได้เหมือนกันกับสิ่งที่เข้าถึงแอตทริบิวต์ในอินสแตนซ์ มันจะทำงานให้classmethodและstaticmethodและอธิบายอื่น ๆ ด้วย มันหลีกเลี่ยงความยุ่งเหยิงใน namespace ด้วยการนำเข้าอื่น
Martijn Pieters

34
รหัสเต็มรูปแบบสำหรับวิธีการอธิบายที่แนะนำคือa.barFighters = barFighters.__get__(a)
eqzx

98

โมดูลใหม่เลิกใช้แล้วตั้งแต่ python 2.6 และถูกลบใน 3.0 ใช้ชนิด

ดูhttp://docs.python.org/library/new.html

ในตัวอย่างด้านล่างฉันตั้งใจลบค่าส่งคืนจากpatch_me()ฟังก์ชัน ฉันคิดว่าการให้ค่าตอบแทนอาจทำให้ใครเชื่อว่า patch ส่งคืนวัตถุใหม่ซึ่งไม่เป็นความจริง - มันปรับเปลี่ยนวัตถุที่เข้ามา น่าจะเป็นสิ่งนี้สามารถช่วยให้การใช้งานการจับลิงเป็นไปอย่างมีวินัยยิ่งขึ้น

import types

class A(object):#but seems to work for old style objects too
    pass

def patch_me(target):
    def method(target,x):
        print "x=",x
        print "called from", target
    target.method = types.MethodType(method,target)
    #add more if needed

a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>  
patch_me(a)    #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6)        #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>

1
มันจะทำงานอย่างไรถ้าวิธีการที่ถูกเพิ่มต้องอ้างถึงตัวเอง? ความพยายามครั้งแรกของฉันนำไปสู่ข้อผิดพลาดทางไวยากรณ์ แต่การเพิ่มตัวเองลงในคำจำกัดความของวิธีการดูเหมือนจะไม่ทำงาน ประเภทการนำเข้าคลาส A (วัตถุ): # แต่ดูเหมือนว่าจะทำงานสำหรับวัตถุสไตล์เก่าเกินไป ax = 'ขวาน' pass def patch_me (เป้าหมาย): วิธีการ def (เป้าหมาย, x): print (self.ax) print ("x =" , x) พิมพ์ ("เรียกจาก" เป้าหมาย) target.method = types.MethodType (เมธอด, เป้าหมาย) # เพิ่มอีกหากจำเป็นต้องพิมพ์ = A () (a.ax)
WesR

85

คำนำ - หมายเหตุเกี่ยวกับความเข้ากันได้: คำตอบอื่น ๆ อาจทำงานได้ใน Python 2 เท่านั้น - คำตอบนี้ควรทำงานได้ดีอย่างสมบูรณ์ใน Python 2 และ 3 หากเขียน Python 3 เท่านั้นคุณอาจไม่ได้รับมรดกโดยชัดแจ้งobjectแต่อย่างอื่น .

การเพิ่มวิธีการไปยังอินสแตนซ์ของวัตถุที่มีอยู่

ฉันอ่านว่ามันเป็นไปได้ที่จะเพิ่มวิธีการในวัตถุที่มีอยู่ (เช่นไม่ได้อยู่ในคำจำกัดความของคลาส) ใน Python

ฉันเข้าใจว่ามันไม่ได้เป็นการตัดสินใจที่ดีเสมอไป แต่คนเราจะทำสิ่งนี้ได้อย่างไร?

ใช่เป็นไปได้ - แต่ไม่แนะนำ

ฉันไม่แนะนำสิ่งนี้ นี่เป็นความคิดที่ไม่ดี อย่าทำมัน

นี่คือเหตุผลสองประการ:

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

ดังนั้นฉันขอแนะนำว่าคุณไม่ควรทำเช่นนี้จนกว่าคุณจะมีเหตุผลที่ดีจริงๆ มันจะดีกว่าที่จะกำหนดวิธีการที่ถูกต้องในการกำหนดระดับหรือน้อยกว่าโดยเฉพาะอย่างยิ่งที่จะแก้ไขชั้นลิงโดยตรงเช่นนี้

Foo.sample_method = sample_method

อย่างไรก็ตามเนื่องจากเป็นคำแนะนำฉันจะแสดงวิธีทำเช่นนี้ให้คุณเห็น

สามารถทำได้อย่างไร

นี่คือรหัสการตั้งค่าบางอย่าง เราต้องการคำจำกัดความของคลาส สามารถนำเข้าได้ แต่มันไม่สำคัญ

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''

สร้างตัวอย่าง:

foo = Foo()

สร้างวิธีการเพิ่มเข้าไป:

def sample_method(self, bar, baz):
    print(bar + baz)

วิธีการศูนย์ (0) - ใช้วิธีการอธิบาย __get__

การค้นหาจุดในฟังก์ชันเรียก__get__เมธอดของฟังก์ชันด้วยอินสแตนซ์เชื่อมวัตถุเข้ากับเมธอดและสร้าง "วิธีผูกมัด"

foo.sample_method = sample_method.__get__(foo)

และตอนนี้:

>>> foo.sample_method(1,2)
3

วิธีที่หนึ่ง - ประเภทวิธีการประเภท

ขั้นแรกให้นำเข้าประเภทที่เราจะได้รับวิธีการสร้าง:

import types

ตอนนี้เราเพิ่มวิธีการในอินสแตนซ์ ในการทำเช่นนี้เราต้องการตัวสร้าง MethodType จากtypesโมดูล (ซึ่งเรานำเข้ามาด้านบน)

ลายเซ็นอาร์กิวเมนต์สำหรับ types.MethodType คือ(function, instance, class):

foo.sample_method = types.MethodType(sample_method, foo, Foo)

และการใช้งาน:

>>> foo.sample_method(1,2)
3

วิธีที่สอง: การรวมคำศัพท์

ก่อนอื่นเราสร้างฟังก์ชั่น wrapper ที่เชื่อมเมธอดเข้ากับอินสแตนซ์:

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn

การใช้งาน:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3

วิธีที่สาม: functools.partial

ฟังก์ชั่นบางส่วนจะใช้อาร์กิวเมนต์แรกกับฟังก์ชัน (และอาร์กิวเมนต์ที่เป็นทางเลือก) และสามารถเรียกใช้ในภายหลังด้วยอาร์กิวเมนต์ที่เหลือ (และแทนที่อาร์กิวเมนต์ของคำหลัก) ดังนั้น:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

สิ่งนี้เหมาะสมเมื่อคุณพิจารณาว่าเมธอดที่ถูกผูกไว้เป็นฟังก์ชันบางส่วนของอินสแตนซ์

ฟังก์ชั่น Unbound เป็นแอตทริบิวต์ object - ทำไมจึงไม่ทำงาน

หากเราพยายามเพิ่ม sample_method ด้วยวิธีเดียวกันกับที่เราอาจเพิ่มเข้าไปในคลาสมันจะถูกดึงออกจากอินสแตนซ์และไม่ใช้ตัวเองโดยนัยเป็นอาร์กิวเมนต์แรก

>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)

เราสามารถทำให้ฟังก์ชั่น unbound ทำงานได้อย่างชัดเจนโดยผ่านอินสแตนซ์ (หรืออะไรก็ได้เนื่องจากวิธีนี้ไม่ได้ใช้selfตัวแปรอาร์กิวเมนต์) แต่มันจะไม่สอดคล้องกับลายเซ็นที่คาดหวังของอินสแตนซ์อื่น ๆ (ถ้าเรากำลังแก้ไขลิง ตัวอย่างนี้):

>>> foo.sample_method(foo, 1, 2)
3

ข้อสรุป

ตอนนี้คุณรู้หลายวิธีที่คุณสามารถทำได้ แต่ในทุกกรณี - อย่าทำอย่างนี้


1
ปฏิเสธความรับผิดชอบคือสิ่งที่ผมสงสัยเกี่ยวกับ นิยามเมธอดเป็นฟังก์ชันที่ซ้อนกันภายในนิยามคลาส
Atcold

1
@tcold ฉันได้ขยายเหตุผลเพื่อหลีกเลี่ยงการทำเช่นนี้ในการแนะนำ
Aaron Hall

วิธีการยังมีความต้องการระดับเป็นพารามิเตอร์ต่อไปนี้:__get__ sample_method.__get__(foo, Foo)
Aidas Bendoraitis

2
@AidasBendoraitis ฉันจะไม่พูดว่า "ต้องการ" มันเป็นพารามิเตอร์ทางเลือกที่ให้มาเมื่อใช้โปรโตคอล descriptor - แต่ฟังก์ชั่น python ไม่ได้ใช้อาร์กิวเมนต์: github.com/python/cpython/blob/master/Objects/funcobject .c # L581
แอรอนฮอลล์

ความคิดเห็นของฉันอ้างอิงจากเอกสารอ้างอิงนี้: python-reference.readthedocs.io/en/latest/docs/dunderdsc/ …ตอนนี้ฉันเห็นอะไรจากเอกสารทางการเป็นทางเลือก: docs.python.org/3/howto/descriptor.html# โปรโตคอลอธิบาย
Aidas Bendoraitis

35

ฉันคิดว่าคำตอบข้างต้นพลาดจุดสำคัญ

มามีคลาสพร้อมเมธอด:

class A(object):
    def m(self):
        pass

ตอนนี้ลองเล่นกับมันใน ipython:

In [2]: A.m
Out[2]: <unbound method A.m>

ตกลงดังนั้นเมตร ()อย่างใดจะกลายเป็นวิธีการที่ไม่ถูกผูกของ แต่มันเป็นอย่างนั้นจริงเหรอ?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

ปรากฎว่าm ()เป็นเพียงฟังก์ชั่นอ้างอิงถึงสิ่งที่ถูกเพิ่มในพจนานุกรมคลาสA - ไม่มีเวทมนตร์ ถ้าเช่นนั้นทำไมAmถึงทำให้เรามีวิธีไม่ผูกมัด? เป็นเพราะจุดไม่ได้ถูกแปลเป็นการค้นหาพจนานุกรมอย่างง่าย อันที่จริงแล้วมันเป็นสายของคลาส. __.__ getattribute __ (A, 'm'):

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

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

ตอนนี้สิ่งที่ __getattribute__ เริ่มต้นทำคือตรวจสอบว่าแอตทริบิวต์นั้นเป็นตัวบอกหรือไม่เช่นถ้ามันใช้วิธีการพิเศษ __get__ ถ้ามันใช้วิธีการที่แล้วสิ่งที่จะถูกส่งกลับเป็นผลจากการเรียกวิธี __get__ ว่า กลับไปสู่รุ่นAรุ่นแรกของเรานี่คือสิ่งที่เรามี:

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

และเนื่องจากฟังก์ชั่น Python ใช้โพรโทคอล descriptor หากมีการเรียกใช้ในนามของวัตถุพวกมันจะผูกตัวเองกับวัตถุนั้นในวิธีการ __get__

ตกลงดังนั้นวิธีการเพิ่มวิธีการไปยังวัตถุที่มีอยู่? สมมติว่าคุณไม่ได้สนใจคลาส patching มันง่ายเหมือน:

B.m = m

จากนั้นBm "จะกลายเป็น" วิธีการที่ไม่ถูกผูกขอบคุณด้วยเวทมนตร์ descriptor

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

b.m = types.MethodType(m, b)

ยังไงซะ:

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>

19

ใน Python Monkey patching นั้นทำงานได้โดยการเขียนทับคลาสหรือฟังก์ชั่นลายมือชื่อของคุณเอง ด้านล่างเป็นตัวอย่างจากZope Wiki :

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak

รหัสนั้นจะเขียนทับ / สร้างวิธีการที่เรียกว่า speak บนคลาส ในเจฟฟ์แอดโพสต์ล่าสุดเมื่อลิง patching เขาแสดงตัวอย่างใน C # 3.0 ซึ่งเป็นภาษาปัจจุบันที่ฉันใช้ในการทำงาน


6
แต่มันมีผลต่ออินสแตนซ์ทั้งหมดของชั้นเรียนไม่ใช่แค่เพียงคลาสเดียว
glglgl

14

คุณสามารถใช้แลมบ์ดาเพื่อผูกเมธอดกับอินสแตนซ์:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()

เอาท์พุท:

This is instance string

9

มีอย่างน้อยสองวิธีในการแนบเมธอดเข้ากับอินสแตนซ์ที่ไม่มีtypes.MethodType:

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>

2:

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>

ลิงค์ที่มีประโยชน์: ตัว
แบบข้อมูล - ตัวอธิบาย descriptor ตัว
อธิบาย HowTo Guide - ตัวอธิบาย descriptor


7

สิ่งที่คุณกำลังมองหาคือsetattrฉันเชื่อ ใช้สิ่งนี้เพื่อตั้งค่าแอตทริบิวต์บนวัตถุ

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>

8
นี้จะปะชั้นไม่อินสแตนซ์A a
Ethan Furman

5
มีเหตุผลที่จะใช้setattr(A,'printme',printme)แทนเพียงA.printme = printme?
โทเบียส Kienzler

1
มันสมเหตุสมผลถ้ามีคนสร้างชื่อวิธีที่ runtime
rr-

6

เนื่องจากคำถามนี้ถามถึงรุ่นที่ไม่ใช่ Python JavaScript ของที่นี่:

a.methodname = function () { console.log("Yay, a new method!") }

5

การรวม Jason Pratt และวิกิชุมชนตอบคำถามด้วยการดูผลลัพธ์ของวิธีการรวมที่แตกต่างกัน:

โดยเฉพาะอย่างยิ่งทราบว่าการเพิ่มฟังก์ชั่นที่มีผลผูกพันเป็นวิธีการเรียนการทำงานแต่ขอบเขตอ้างอิงไม่ถูกต้อง

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>

โดยส่วนตัวแล้วฉันชอบเส้นทางของฟังก์ชั่น ADDMETHOD ภายนอกซึ่งทำให้ฉันสามารถกำหนดชื่อเมธอดใหม่แบบไดนามิกภายในตัววนซ้ำได้เช่นกัน

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>

addmethodการเขียนในลักษณะต่อไปนี้def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )จะช่วยแก้ปัญหา
แอนโทนี Hatchkins

5

นี่เป็นส่วนเสริมของคำตอบของ "Jason Pratt"

ถึงแม้ว่าคำตอบของ Jasons จะใช้ได้ แต่จะใช้ได้ก็ต่อเมื่อต้องการเพิ่มฟังก์ชันลงในคลาส มันไม่ทำงานสำหรับฉันเมื่อฉันพยายามโหลดวิธีที่มีอยู่แล้วจากไฟล์ซอร์ส. py

ฉันใช้เวลานานหลายปีในการหาวิธีแก้ปัญหา แต่เคล็ดลับดูง่าย ... 1. นำเข้าโค้ดจากไฟล์ซอร์สโค้ด 2. บังคับให้โหลดรีโหลด 3. ใช้ type.FunctionType (... ) เพื่อแปลง วิธีที่นำเข้าและผูกไว้กับฟังก์ชั่นที่คุณสามารถส่งผ่านตัวแปรทั่วโลกในปัจจุบันเป็นวิธีการโหลดจะอยู่ใน namespace ที่แตกต่างกัน 4 ตอนนี้คุณสามารถดำเนินการต่อตามที่แนะนำโดย "Jason Pratt" โดยใช้ types.MethodType (... )

ตัวอย่าง:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"

    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code

3

หากสามารถช่วยได้ฉันเพิ่งเปิดตัว Python library ชื่อ Gorilla เพื่อให้กระบวนการของการปะแก้ลิงสะดวกยิ่งขึ้น

การใช้ฟังก์ชั่นneedle()เพื่อแก้ไขโมดูลที่มีชื่อguineapigจะเป็นดังนี้:

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

แต่มันก็ยังดูแลกรณีการใช้งานที่น่าสนใจมากขึ้นดังที่แสดงในคำถามที่พบบ่อยจากเอกสาร

รหัสที่มีอยู่ในGitHub


3

คำถามนี้เปิดเมื่อหลายปีก่อน แต่เดี๋ยวก่อนมีวิธีง่าย ๆ ในการจำลองการเชื่อมโยงของฟังก์ชันกับอินสแตนซ์ของคลาสโดยใช้มัณฑนากร:

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()

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

โดยสรุปคุณได้รับฟังก์ชั่นการจำลองมันผูกพันกับตัวอย่างของชั้น ปล่อยให้ฟังก์ชั่นดั้งเดิมไม่เปลี่ยนแปลง


2

สิ่งที่ Jason Pratt โพสต์นั้นถูกต้อง

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

อย่างที่คุณเห็น Python ไม่พิจารณา b () แตกต่างจาก a () ใน Python วิธีการทั้งหมดเป็นเพียงตัวแปรที่เกิดขึ้นเป็นฟังก์ชั่น


7
คุณกำลังปรับปรุงคลาสTestไม่ใช่อินสแตนซ์ของมัน
Ethan Furman

คุณกำลังเพิ่มวิธีการในชั้นเรียนไม่ใช่วัตถุเช่น
TomSawyer

2

ฉันคิดว่ามันแปลกที่ไม่มีใครพูดถึงวิธีการทั้งหมดที่กล่าวมาข้างต้นสร้างการอ้างอิงรอบระหว่างวิธีการเพิ่มและตัวอย่างทำให้วัตถุยังคงอยู่จนกว่าจะมีการเก็บขยะ มีกลอุบายเก่า ๆ ที่เพิ่ม descriptor โดยขยายคลาสของวัตถุ:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass

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