Python: thay đổi phương thức và thuộc tính trong thời gian chạy


76

Tôi muốn tạo một lớp bằng Python mà tôi có thể thêm và xóa các thuộc tính và phương thức. Làm thế nào tôi có thể biên tập điều đó?

Ồ, và xin đừng hỏi tại sao.



3
Bạn muốn tìm hiểu cách đấm vịt vào trăn? vi.wikipedia.org/wiki/Duck_punching
baudtack

1
upvoted cho hỏi không hỏi tại sao
oulenz

Câu trả lời:


47

Tôi muốn tạo một lớp bằng Python mà tôi có thể thêm và xóa các thuộc tính và phương thức.

import types

class SpecialClass(object):
    @classmethod
    def removeVariable(cls, name):
        return delattr(cls, name)

    @classmethod
    def addMethod(cls, func):
        return setattr(cls, func.__name__, types.MethodType(func, cls))

def hello(self, n):
    print n

instance = SpecialClass()
SpecialClass.addMethod(hello)

>>> SpecialClass.hello(5)
5

>>> instance.hello(6)
6

>>> SpecialClass.removeVariable("hello")

>>> instance.hello(7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'SpecialClass' object has no attribute 'hello'

>>> SpecialClass.hello(8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'SpecialClass' has no attribute 'hello'

9
Lưu ý rằng điều này thêm một phương thức lớp vào SpecialClass. Nó không thêm một phương thức sẽ có sẵn cho tất cả các phiên bản SpecialClass trong tương lai. (Tôi tự hỏi liệu có cách nào để làm điều đó không .) _
M. Elkstein

Điều đó sẽ thực sự thú vị.
Techllogue,

123

Ví dụ này cho thấy sự khác biệt giữa việc thêm một phương thức vào một lớp và một thể hiện.

>>> class Dog():
...     def __init__(self, name):
...             self.name = name
...
>>> skip = Dog('Skip')
>>> spot = Dog('Spot')
>>> def talk(self):
...     print 'Hi, my name is ' + self.name
...
>>> Dog.talk = talk # add method to class
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk()
Hi, my name is Spot
>>> del Dog.talk # remove method from class
>>> skip.talk() # won't work anymore
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'
>>> import types
>>> f = types.MethodType(talk, skip, Dog)
>>> skip.talk = f # add method to specific instance
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk() # won't work, since we only modified skip
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'

13
Lưu ý rằng bạn chỉ có thể thực hiện việc này đối với các lớp , không phải phiên bản . Nếu bạn thực hiện dog.talk = talk, talk sẽ không phải là một "phương pháp ràng buộc", nghĩa là nó sẽ không nhận được lập luận ngầm "self".
Paul Fisher

10
Để tăng thêm bình luận Phaolô: nếu bạn muốn monkeypatch một phương pháp dụ: "các loại nhập khẩu; f = types.MethodType (nói chuyện, con chó con, Chó); puppy.talk = f"
Jarret Hardie

5
+1 cho Paolo để chứng minh hiệu ứng động của việc gán và xóa các thuộc tính của phương thức lớp.
Jarret Hardie

3
Cảm ơn những lời bình luận tuyệt vời, tôi đã cập nhật câu trả lời để hiển thị sự khác biệt.
Paolo Bergantino

8
Ví dụ đã được chỉnh sửa rất hay ... nó gần như sẽ có trong tài liệu API Python cho mô-đun loại, điều này không đầy đủ.
Jarret Hardie

28

Một sự thay thế có thể thú vị để sử dụng types.MethodTypetrong:

>>> f = types.MethodType(talk, puppy, Dog)
>>> puppy.talk = f # add method to specific instance

sẽ là khai thác thực tế rằng các hàm là bộ mô tả :

>>> puppy.talk = talk.__get__(puppy, Dog)

2
Tôi vừa học được vài thứ :) Nhưng tôi nghĩ rằng nó có vẻ ít đọc hơn.
Nicolas Dumazet

+1 Cú pháp thay thế tốt, như bạn nói. Tôi tò mò: có bất kỳ lợi ích cụ thể nào đối với cách tiếp cận này hay việc sử dụng "loại" không? Cuối cùng, chúng tạo ra cùng một kết quả và các liên kết bên trong AFAICAT. Các loại.MethodType có tạo ra một bộ mô tả một cách hiệu quả hay còn nhiều hơn thế nữa?
Jarret Hardie

@NicDumZ, vâng, những thứ __ không bao giờ thực sự trông đẹp. @Jarret, đã có lúc nào đó trong thiết kế của Python 3 có cuộc nói chuyện lỏng lẻo về việc loại bỏ mô-đun 'loại', nhưng nó vẫn ở lại, giảm từ 37 mục xuống còn 12 (mô-đun 'mới' đã biến mất, yay! -). Về mặt ngữ nghĩa chúng thực sự giống nhau: MethodType trả về cùng một loại đối tượng là kết quả của get - một thể hiện của <type 'instancemethod'>.
Alex Martelli

5

Tôi muốn tạo một lớp bằng Python mà tôi có thể thêm và xóa các thuộc tính và phương thức. Làm thế nào tôi có thể biên tập điều đó?

Bạn có thể thêm và xóa các thuộc tính và phương thức cho bất kỳ lớp nào và chúng sẽ có sẵn cho tất cả các phiên bản của lớp:

>>> def method1(self):
       pass

>>> def method1(self):
       print "method1"

>>> def method2(self):
       print "method2"

>>> class C():
       pass

>>> c = C()
>>> c.method()

Traceback (most recent call last):
  File "<pyshell#62>", line 1, in <module>
    c.method()
AttributeError: C instance has no attribute 'method'

>>> C.method = method1
>>> c.method()
    method1
>>> C.method = method2
>>> c.method()
    method2
>>> del C.method
>>> c.method()

Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    c.method()
AttributeError: C instance has no attribute 'method'
>>> C.attribute = "foo"
>>> c.attribute
    'foo'
>>> c.attribute = "bar"
>>> c.attribute
    'bar'

4

bạn có thể chỉ định trực tiếp cho lớp (bằng cách truy cập vào tên lớp ban đầu hoặc thông qua __class__):

class a : pass
ob=a()
ob.__class__.blah=lambda self,k: (3, self,k)
ob.blah(5)
ob2=a()
ob2.blah(7)

sẽ in

(3, <__main__.a instance at 0x7f18e3c345f0>, 5)
(3, <__main__.a instance at 0x7f18e3c344d0>, 7)

0

một thay thế khác, nếu bạn cần thay thế bán buôn lớp là sửa đổi thuộc tính lớp :

>>> class A(object):
...     def foo(self):
...         print 'A'
... 
>>> class B(object):
...     def foo(self):
...         print 'Bar'
... 
>>> a = A()
>>> a.foo()
A
>>> a.__class__ = B
>>> a.foo()
Bar

Thật thú vị, nhưng vấn đề là sửa đổi các phương thức @ runtime. Đây trông giống như một ý tưởng cho IoC container với chuyển mạch thời gian chạy :)
Migol

Có, lớp chuyển mạch cho phép bạn sửa đổi bán buôn các phương thức, đặc biệt là khi bạn thêm điều này với thực tế là python cho phép đa kế thừa và các lớp python có thể thay đổi, nó có thể dẫn đến một số kỹ thuật động khá mạnh hoặc mã rất khó hiểu.
Lie Ryan

0

Đơn giản:

f1 = lambda:0                   #method for instances
f2 = lambda _:0                 #method for class
class C: pass                   #class

c1,c2 = C(),C()                 #instances

print dir(c1),dir(c2)

#add to the Instances
c1.func = f1
c1.any = 1.23

print dir(c1),dir(c2)
print c1.func(),c1.any

del c1.func,c1.any

#add to the Class
C.func = f2
C.any = 1.23

print dir(c1),dir(c2)
print c1.func(),c1.any
print c2.func(),c2.any

kết quả là:

['__doc__', '__module__'] ['__doc__', '__module__']
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__']
0 1.23
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__', 'any', 'func']
0 1.23
0 1.23

0

Bản thân lớp có nhất thiết phải sửa đổi không? Hay mục tiêu chỉ đơn giản là thay thế đối tượng.method () nào thực hiện tại một điểm cụ thể trong thời gian chạy?

Tôi hỏi vì tôi đã tránh được vấn đề thực sự sửa đổi lớp thành các cuộc gọi phương thức cụ thể vá khỉ trong khuôn khổ của tôi với getattribute và Runtime Decorator trên đối tượng kế thừa Cơ sở của tôi.

Các phương thức được truy xuất bởi một đối tượng Cơ sở trong getattribute được bao bọc trong một Runtime_Decorator để phân tích cú pháp phương thức gọi các đối số từ khóa cho các trình trang trí / các bản vá khỉ áp dụng.

Điều này cho phép bạn sử dụng cú pháp object.method (Monkey_patch = "mypatch"), object.method (decorator = "mydecorator") và thậm chí object.method (decorators = my_decorator_list).

Điều này hoạt động cho bất kỳ lệnh gọi phương thức riêng lẻ nào (tôi bỏ qua các phương thức ma thuật), làm như vậy mà không thực sự sửa đổi bất kỳ thuộc tính lớp / phiên bản nào, có thể sử dụng các phương thức tùy ý, thậm chí là ngoại lai để vá và sẽ hoạt động minh bạch trên các lớp con kế thừa từ Base (miễn là chúng không tất nhiên không ghi đè getattribute ).

import trace

def monkey_patched(self, *args, **kwargs):
    print self, "Tried to call a method, but it was monkey patched instead"
    return "and now for something completely different"

class Base(object):

    def __init__(self):
        super(Base, self).__init__()

    def testmethod(self):
        print "%s test method" % self

    def __getattribute__(self, attribute):
        value = super(Base, self).__getattribute__(attribute)
        if "__" not in attribute and callable(value):
            value = Runtime_Decorator(value)
        return value

class Runtime_Decorator(object):

    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):

        if kwargs.has_key("monkey_patch"):
            module_name, patch_name = self._resolve_string(kwargs.pop("monkey_patch"))
            module = self._get_module(module_name)
            monkey_patch = getattr(module, patch_name)
            return monkey_patch(self.function.im_self, *args, **kwargs)

        if kwargs.has_key('decorator'):
            decorator_type = str(kwargs['decorator'])

            module_name, decorator_name = self._resolve_string(decorator_type)
            decorator = self._get_decorator(decorator_name, module_name)
            wrapped_function = decorator(self.function)
            del kwargs['decorator']
            return wrapped_function(*args, **kwargs)

        elif kwargs.has_key('decorators'):
            decorators = []

            for item in kwargs['decorators']:
                module_name, decorator_name = self._resolve_string(item)
                decorator = self._get_decorator(decorator_name, module_name)
                decorators.append(decorator)

            wrapped_function = self.function
            for item in reversed(decorators):
                wrapped_function = item(wrapped_function)
            del kwargs['decorators']
            return wrapped_function(*args, **kwargs)

        else:
            return self.function(*args, **kwargs)

    def _resolve_string(self, string):
        try: # attempt to split the string into a module and attribute
            module_name, decorator_name = string.split(".")
        except ValueError: # there was no ".", it's just a single attribute
            module_name = "__main__"
            decorator_name = string
        finally:
            return module_name, decorator_name

    def _get_module(self, module_name):
        try: # attempt to load the module if it exists already
            module = modules[module_name]
        except KeyError: # import it if it doesn't
            module = __import__(module_name)
        finally:
            return module

    def _get_decorator(self, decorator_name, module_name):
        module = self._get_module(module_name)
        try: # attempt to procure the decorator class
            decorator_wrap = getattr(module, decorator_name)
        except AttributeError: # decorator not found in module
            print("failed to locate decorators %s for function %s." %\
            (kwargs["decorator"], self.function))
        else:
            return decorator_wrap # instantiate the class with self.function

class Tracer(object):

    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        tracer = trace.Trace(trace=1)
        tracer.runfunc(self.function, *args, **kwargs)

b = Base()
b.testmethod(monkey_patch="monkey_patched")
b.testmethod(decorator="Tracer")
#b.testmethod(monkey_patch="external_module.my_patch")

Nhược điểm của phương pháp này là getattribute móc tất cả quyền truy cập vào các thuộc tính, vì vậy việc kiểm tra và gói phương thức tiềm năng xảy ra ngay cả đối với các thuộc tính không phải là phương thức + sẽ không sử dụng tính năng cho lệnh gọi cụ thể được đề cập. Và việc sử dụng getattribute vốn dĩ hơi phức tạp.

Tác động thực tế của chi phí này trong trải nghiệm của tôi / cho mục đích của tôi là không đáng kể và máy của tôi chạy Celeron lõi kép. Việc triển khai trước đó, tôi đã sử dụng các phương thức nội quan dựa trên đối tượng init và sau đó ràng buộc Runtime_Decorator với các phương thức. Làm những việc theo cách đó đã loại bỏ nhu cầu sử dụng getattribute và giảm chi phí đã đề cập trước đây ... tuy nhiên, nó cũng phá vỡ dây dưa (có thể không thì là) và kém năng động hơn so với cách tiếp cận này.

Các trường hợp sử dụng duy nhất mà tôi thực sự gặp phải "trong tự nhiên" với kỹ thuật này là tính thời gian và truy tìm trang trí. Tuy nhiên, khả năng nó mở ra là vô cùng rộng lớn.

Nếu bạn có một lớp tồn tại trước đó không thể được thực hiện để kế thừa từ một cơ sở khác (hoặc sử dụng kỹ thuật định nghĩa lớp của chính nó hoặc trong lớp cơ sở của nó '), thì rất tiếc toàn bộ điều không áp dụng cho vấn đề của bạn.

Tôi không nghĩ rằng việc thiết lập / xóa các thuộc tính không thể gọi trên một lớp trong thời gian chạy có nhất thiết phải là một thách thức như vậy không? trừ khi bạn muốn các lớp kế thừa từ lớp đã sửa đổi cũng tự động phản ánh những thay đổi trong chính chúng ... Mặc dù vậy, đó sẽ là một con sâu 'nother can o' hoàn toàn bởi âm thanh của nó.

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.