Trang trí Python trong các lớp học


140

Người ta có thể viết một cái gì đó như:

class Test(object):
    def _decorator(self, foo):
        foo()

    @self._decorator
    def bar(self):
        pass

Điều này không thành công: self in @elf không xác định

Tôi cũng đã thử:

@Test._decorator(self)

Điều này cũng thất bại: Kiểm tra không rõ

Tôi muốn tạm thời thay đổi một số biến thể hiện trong trình trang trí và sau đó chạy phương thức trang trí, trước khi thay đổi chúng trở lại.

Câu trả lời:


268

Một cái gì đó như thế này sẽ làm những gì bạn cần?

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

test = Test()

test.bar()

Điều này tránh được lệnh gọi tự truy cập trang trí và để nó ẩn trong không gian tên lớp như một phương thức thông thường.

>>> import stackoverflow
>>> test = stackoverflow.Test()
>>> test.bar()
start magic
normal call
end magic
>>> 

chỉnh sửa để trả lời câu hỏi trong ý kiến:

Làm thế nào để sử dụng trang trí ẩn trong lớp khác

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

    _decorator = staticmethod( _decorator )

class TestB( Test ):
    @Test._decorator
    def bar( self ):
        print "override bar in"
        super( TestB, self ).bar()
        print "override bar out"

print "Normal:"
test = Test()
test.bar()
print

print "Inherited:"
b = TestB()
b.bar()
print

Đầu ra:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic

7
Các trang trí hoặc các chức năng trang trí? Lưu ý hàm "ma thuật" được trả về mà thanh kết thúc đang nhận một biến tự ở trên khi "thanh" được gọi trên một thể hiện và có thể làm bất cứ điều gì với các biến đối tượng mà nó muốn trước và sau (hoặc thậm chí là hay không) nó được gọi là "bar" . Không có thứ gọi là biến thể hiện khi khai báo lớp. Bạn có muốn làm một cái gì đó cho lớp từ bên trong trang trí? Tôi không nghĩ rằng đó là một cách sử dụng thành ngữ.
Michael Speer

1
Cảm ơn Michael, chỉ bây giờ thấy rằng đây là những gì tôi muốn.
hcvst

2
Tôi thấy giải pháp này đẹp hơn nhiều so với câu trả lời được chấp nhận, bởi vì nó cho phép sử dụng cú pháp @ decorator tại điểm định nghĩa. Nếu tôi phải đặt các cuộc gọi trang trí vào cuối lớp, thì không rõ các chức năng đang được trang trí. Có một điều kỳ lạ là bạn không thể sử dụng @staticmethod trên chính trang trí, nhưng ít nhất nó cũng hoạt động.
mgiuca

1
Tôi không nghĩ nó hoạt động nếu tôi tạo một lớp Test được kế thừa. Ví dụ: class TestB (Test): @_decorator def foobar (self): print "adsf" Có cách giải quyết không?
ngoại lệ

1
@extraeee: kiểm tra chỉnh sửa tôi đã thực hiện. bạn cần phải đủ điều kiện trang trí đã cho là tĩnh, nhưng chỉ sau khi bạn sử dụng xong (hoặc gán phiên bản tĩnh cho một tên khác)
Michael Speer

58

Những gì bạn muốn làm là không thể. Lấy ví dụ, liệu mã bên dưới có hợp lệ hay không:

class Test(object):

    def _decorator(self, foo):
        foo()

    def bar(self):
        pass
    bar = self._decorator(bar)

Tất nhiên, nó không hợp lệ vì selfkhông được xác định tại thời điểm đó. Điều tương tự cũng xảy ra Testvì nó sẽ không được định nghĩa cho đến khi chính lớp được định nghĩa (mà nó đang trong quá trình). Tôi đang cho bạn xem đoạn mã này bởi vì đây là đoạn mã trang trí của bạn biến thành.

Vì vậy, như bạn có thể thấy, việc truy cập vào thể hiện trong một trình trang trí như thế là không thực sự có thể vì các trình trang trí được áp dụng trong định nghĩa của bất kỳ chức năng / phương thức nào mà chúng được gắn vào và không trong thời gian khởi tạo.

Nếu bạn cần truy cập cấp lớp , hãy thử điều này:

class Test(object):

    @classmethod
    def _decorator(cls, foo):
        foo()

    def bar(self):
        pass
Test.bar = Test._decorator(Test.bar)

5
có lẽ nên được cập nhật để tham khảo câu trả lời chính xác hơn dưới đây
Nathan Buesgens

1
Đẹp. Văn xuôi của bạn nói không thể, nhưng mã của bạn khá nhiều cho thấy làm thế nào để làm điều đó.
Nhà vật lý điên

22
import functools


class Example:

    def wrapper(func):
        @functools.wraps(func)
        def wrap(self, *args, **kwargs):
            print("inside wrap")
            return func(self, *args, **kwargs)
        return wrap

    @wrapper
    def method(self):
        print("METHOD")

    wrapper = staticmethod(wrapper)


e = Example()
e.method()

1
LoạiError: đối tượng 'staticmethod' không thể gọi được
wyx

@wyx đừng gọi người trang trí. Ví dụ, nó phải là @foo, không@foo()
docyoda

Không phải là đối số đầu tiên wrapperphải selfkhông?
CpILL

7

Tôi sử dụng kiểu trang trí này trong một số tình huống gỡ lỗi, nó cho phép ghi đè các thuộc tính lớp bằng cách trang trí, mà không phải tìm chức năng gọi.

class myclass(object):
    def __init__(self):
        self.property = "HELLO"

    @adecorator(property="GOODBYE")
    def method(self):
        print self.property

Đây là mã trang trí

class adecorator (object):
    def __init__ (self, *args, **kwargs):
        # store arguments passed to the decorator
        self.args = args
        self.kwargs = kwargs

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

            #the 'self' for a method function is passed as args[0]
            slf = args[0]

            # replace and store the attributes
            saved = {}
            for k,v in self.kwargs.items():
                if hasattr(slf, k):
                    saved[k] = getattr(slf,k)
                    setattr(slf, k, v)

            # call the method
            ret = func(*args, **kwargs)

            #put things back
            for k,v in saved.items():
                setattr(slf, k, v)

            return ret
        newf.__doc__ = func.__doc__
        return newf 

Lưu ý: bởi vì tôi đã sử dụng trình trang trí lớp, bạn sẽ cần sử dụng @adecorator () với dấu ngoặc trên để trang trí các chức năng, ngay cả khi bạn không chuyển bất kỳ đối số nào cho trình tạo lớp trang trí.


7

Đây là một cách để truy cập (và đã sử dụng) selftừ bên trong một decoratorđịnh nghĩa bên trong cùng một lớp:

class Thing(object):
    def __init__(self, name):
        self.name = name

    def debug_name(function):
        def debug_wrapper(*args):
            self = args[0]
            print 'self.name = ' + self.name
            print 'running function {}()'.format(function.__name__)
            function(*args)
            print 'self.name = ' + self.name
        return debug_wrapper

    @debug_name
    def set_name(self, new_name):
        self.name = new_name

Đầu ra (đã thử nghiệm Python 2.7.10):

>>> a = Thing('A')
>>> a.name
'A'
>>> a.set_name('B')
self.name = A
running function set_name()
self.name = B
>>> a.name
'B'

Ví dụ trên là ngớ ngẩn, nhưng nó hoạt động.


4

Tôi tìm thấy câu hỏi này trong khi nghiên cứu một vấn đề rất giống nhau. Giải pháp của tôi là chia vấn đề thành hai phần. Đầu tiên, bạn cần nắm bắt dữ liệu mà bạn muốn liên kết với các phương thức lớp. Trong trường hợp này, handler_for sẽ liên kết một lệnh Unix với trình xử lý cho đầu ra của lệnh đó.

class OutputAnalysis(object):
    "analyze the output of diagnostic commands"
    def handler_for(name):
        "decorator to associate a function with a command"
        def wrapper(func):
            func.handler_for = name
            return func
        return wrapper
    # associate mount_p with 'mount_-p.txt'
    @handler_for('mount -p')
    def mount_p(self, slurped):
        pass

Bây giờ chúng ta đã liên kết một số dữ liệu với từng phương thức lớp, chúng ta cần thu thập dữ liệu đó và lưu trữ nó trong một thuộc tính lớp.

OutputAnalysis.cmd_handler = {}
for value in OutputAnalysis.__dict__.itervalues():
    try:
        OutputAnalysis.cmd_handler[value.handler_for] = value
    except AttributeError:
        pass

4

Đây là một bản mở rộng về câu trả lời của Michael Sperer để tiến thêm một vài bước:

Một trình trang trí phương thức cá thể nhận các đối số và hành động trên một hàm với các đối số và giá trị trả về.

class Test(object):
    "Prints if x == y. Throws an error otherwise."
    def __init__(self, x):
        self.x = x

    def _outer_decorator(y):
        def _decorator(foo):
            def magic(self, *args, **kwargs) :
                print("start magic")
                if self.x == y:
                    return foo(self, *args, **kwargs)
                else:
                    raise ValueError("x ({}) != y ({})".format(self.x, y))
                print("end magic")
            return magic

        return _decorator

    @_outer_decorator(y=3)
    def bar(self, *args, **kwargs) :
        print("normal call")
        print("args: {}".format(args))
        print("kwargs: {}".format(kwargs))

        return 27

Và sau đó

In [2]:

    test = Test(3)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    normal call
    args: (13, 'Test')
    kwargs: {'q': 9, 'lollipop': [1, 2, 3]}
Out[2]:
    27
In [3]:

    test = Test(4)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-3-576146b3d37e> in <module>()
          4     'Test',
          5     q=9,
    ----> 6     lollipop=[1,2,3]
          7 )

    <ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs)
         11                     return foo(self, *args, **kwargs)
         12                 else:
    ---> 13                     raise ValueError("x ({}) != y ({})".format(self.x, y))
         14                 print("end magic")
         15             return magic

    ValueError: x (4) != y (3)

3

Các nhà trang trí có vẻ phù hợp hơn để sửa đổi chức năng của toàn bộ một đối tượng (bao gồm các đối tượng chức năng) so với chức năng của một phương thức đối tượng mà nói chung sẽ phụ thuộc vào các thuộc tính thể hiện. Ví dụ:

def mod_bar(cls):
    # returns modified class

    def decorate(fcn):
        # returns decorated function

        def new_fcn(self):
            print self.start_str
            print fcn(self)
            print self.end_str

        return new_fcn

    cls.bar = decorate(cls.bar)
    return cls

@mod_bar
class Test(object):
    def __init__(self):
        self.start_str = "starting dec"
        self.end_str = "ending dec" 

    def bar(self):
        return "bar"

Đầu ra là:

>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec

1

Bạn có thể trang trí trang trí:

import decorator

class Test(object):
    @decorator.decorator
    def _decorator(foo, self):
        foo(self)

    @_decorator
    def bar(self):
        pass

1

Tôi có một triển khai trang trí có thể giúp

    import functools
    import datetime


    class Decorator(object):

        def __init__(self):
            pass


        def execution_time(func):

            @functools.wraps(func)
            def wrap(self, *args, **kwargs):

                """ Wrapper Function """

                start = datetime.datetime.now()
                Tem = func(self, *args, **kwargs)
                end = datetime.datetime.now()
                print("Exection Time:{}".format(end-start))
                return Tem

            return wrap


    class Test(Decorator):

        def __init__(self):
            self._MethodName = Test.funca.__name__

        @Decorator.execution_time
        def funca(self):
            print("Running Function : {}".format(self._MethodName))
            return True


    if __name__ == "__main__":
        obj = Test()
        data = obj.funca()
        print(data)

1

Tuyên bố trong lớp học bên trong. Giải pháp này là khá vững chắc và được đề nghị.

class Test(object):
    class Decorators(object):
    @staticmethod
    def decorator(foo):
        def magic(self, *args, **kwargs) :
            print("start magic")
            foo(self, *args, **kwargs)
            print("end magic")
        return magic

    @Decorators.decorator
    def bar( self ) :
        print("normal call")

test = Test()

test.bar()

Kết quả:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
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.