Câu hỏi Python - tôi có thể lập trình trang trí các phương thức lớp từ một cá thể lớp không?


Tôi có một hệ thống phân cấp đối tượng trong đó hầu như tất cả các phương thức đều là các phương thức lớp. Nó trông giống như sau:

class ParentObject(object):
    def __init__(self):
        pass

    @classmethod
    def smile_warmly(cls, the_method):
        def wrapper(kls, *args, **kwargs):
            print "-smile_warmly - "+kls.__name__
            the_method(*args, **kwargs)
        return wrapper

    @classmethod
    def greetings(cls):
        print "greetings"

class SonObject(ParentObject):
    @classmethod
    def hello_son(cls):
        print "hello son"

    @classmethod
    def goodbye(cls):
        print "goodbye son"

class DaughterObject(ParentObject):
    @classmethod
    def hello_daughter(cls):
        print "hello daughter"

    @classmethod
    def goodbye(cls):
        print "goodbye daughter"

if __name__ == '__main__':
    son = SonObject()
    son.greetings()
    son.hello_son()
    son.goodbye()
    daughter = DaughterObject()
    daughter.greetings()
    daughter.hello_daughter()
    daughter.goodbye()

Mã như đầu ra đã cho như sau:

greetings
hello son
goodbye son
greetings
hello daughter
goodbye daughter

Tôi muốn mã xuất ra như sau:

-smile_warmly - SonObject
greetings
-smile_warmly - SonObject
hello son
-smile_warmly - SonObject
goodbye son
-smile_warmly - DaughterObject
greetings
-smile_warmly - DaughterObject
hello daughter
-smile_warmly - DaughterObject
goodbye daughter

Nhưng tôi không muốn thêm dòng @smile_warmly trước mỗi phương thức (và khi tôi cố gắng làm điều đó trong đoạn mã trên, tôi nhận được thông báo lỗi TypeError: 'classmethod' object is not callable). Thay vào đó, tôi muốn trang trí của từng phương pháp diễn ra theo chương trình trong __init__() phương pháp.

Có thể lập trình trang trí các phương thức bằng Python không?

CHỈNH SỬA: Tìm thấy một cái gì đó mà dường như làm việc - xem câu trả lời của tôi dưới đây. Cảm ơn BrenBarn.


9
2017-12-30 23:05


gốc


Khái niệm metaclasses có quen thuộc với bạn không? - Matti Virkkunen
Có vẻ như bạn sẽ muốn xem xét điều này: stackoverflow.com/questions/100003/…  Có lẽ nếu bạn được thiết lập về sử dụng trang trí anh chàng này quá: stackoverflow.com/questions/739654/… - Anthony Sottile
Tôi đã nghe nói về họ, nhưng đó là về nó. Vậy là không. - jononomo


Các câu trả lời:


Tất cả một trang trí không là trả lại một chức năng mới. Điều này:

@deco
def foo():
    # blah

giống như thế này:

def foo():
    # blah
foo = deco(foo)

Bạn có thể làm điều tương tự bất cứ khi nào bạn thích, không có @ cú pháp, chỉ bằng cách thay thế các hàm bằng bất cứ thứ gì bạn thích. Vì vậy, trong __init__ hoặc bất cứ nơi nào khác, bạn có thể lặp qua tất cả các phương pháp và cho mỗi phương án thay thế bằng smilewarmly(meth).

Tuy nhiên, thay vì thực hiện nó trong __init__, nó sẽ có ý nghĩa hơn để làm điều đó khi lớp được tạo ra. Bạn có thể làm điều này với một metaclass, hoặc đơn giản hơn với một trang trí lớp:

def smileDeco(func):
    def wrapped(*args, **kw):
        print ":-)"
        func(*args, **kw)
    return classmethod(wrapped)

def makeSmiley(cls):
    for attr, val in cls.__dict__.iteritems():
        if callable(val) and not attr.startswith("__"):
            setattr(cls, attr, smileDeco(val))
    return cls

@makeSmiley
class Foo(object):
    def sayStuff(self):
        print "Blah blah"

>>> Foo().sayStuff()
:-)
Blah blah

Trong ví dụ này tôi đặt trang trí classmethod bên trong của tôi smileDeco trang trí. Bạn cũng có thể đặt nó vào makeSmiley để vậy makeSmiley trả lại smileDeco(classmethod(val)). (Cách nào bạn muốn làm điều đó phụ thuộc vào cách liên kết chặt chẽ với trình trang trí bằng nụ cười với những thứ là classmethods.) Điều này có nghĩa là bạn không phải sử dụng @classmethod bên trong lớp.

Ngoài ra, tất nhiên, trong vòng lặp trong makeSmiley bạn có thể bao gồm bất kỳ logic nào bạn muốn quyết định (ví dụ, dựa trên tên của phương thức) cho dù có quấn nó với hành vi nụ cười hay không.

Lưu ý rằng bạn phải cẩn thận hơn một chút nếu bạn thực sự muốn sử dụng theo cách thủ công @classmethod bên trong lớp, vì classmethods được truy cập thông qua lớp __dict__ không thể gọi được. Vì vậy, bạn phải kiểm tra cụ thể xem đối tượng có phải là một đối tượng classmethod hay không, thay vì chỉ kiểm tra xem nó có thể gọi được hay không.


17
2017-12-30 23:21



Điều này có vẻ rất hữu ích. Tôi đang cố gắng làm cho nó hoạt động ngay bây giờ và sẽ cung cấp một bản cập nhật ở đây nếu tôi có thể kéo nó ra. - jononomo
Nếu tôi có hàng chục lớp học phân lớp lớp cha mẹ của tôi, sau đó tôi phải nhớ để trang trí mỗi lớp. Tôi muốn chỉ có logic điều chỉnh các phương thức xảy ra tự động trong __init__() phương pháp. Các lớp con của tôi sẽ không ghi đè __init __ (), vì vậy __init__() được định nghĩa trong lớp cha sẽ luôn được gọi khi lớp con được xây dựng. Nếu tôi có thể đặt logic tương tự ở đó, thì tôi sẽ có chức năng mà tôi mong muốn. Đó là những gì tôi đang làm. - jononomo
@ JonCrowell: Bạn có thể làm điều đó, nhưng __init__ được gọi cho mọi ví dụ, không phải mọi lớp, vì vậy nó không giống như những gì bạn muốn. Nếu bạn muốn hành vi hoàn toàn tự động, bạn sẽ phải sử dụng một metaclass. Bạn có thể đọc trên metaclasses đây và đây. - BrenBarn
Tôi có một hệ thống phân cấp lớp, trong đó tôi muốn tất cả các phương thức là các phương thức lớp. Tôi gần như có những gì tôi muốn, nhờ vào con trỏ của bạn. Xem chỉnh sửa của tôi cho câu hỏi ban đầu của tôi. Tôi sẽ đánh dấu câu trả lời của bạn là chính xác. - jononomo
Xem câu trả lời của tôi dưới đây, trong đó sản xuất sản lượng tôi mong muốn. - jononomo


Giải pháp này tạo ra sản lượng mà tôi mong muốn:

class ParentObject(object):
    def __init__(self):
        self._adjust_methods(self.__class__)

    def _adjust_methods(self, cls):
        for attr, val in cls.__dict__.iteritems():
            if callable(val) and not attr.startswith("_"):
                setattr(cls, attr, self._smile_warmly(val))
        bases = cls.__bases__
        for base in bases:
            self._adjust_methods(base)

    def _smile_warmly(self, the_method):
        def _wrapped(self, *args, **kwargs):
            print "-smile_warmly - " +self.__name__
            the_method(self, *args, **kwargs)
        cmethod_wrapped = classmethod(_wrapped)
        # cmethod_wrapped.adjusted = True
        return cmethod_wrapped

    def greetings(self):
        print "greetings"

class SonObject(ParentObject):
    def hello_son(self):
        print "hello son"

    def goodbye(self):
        print "goodbye son"

class DaughterObject(ParentObject):
    def hello_daughter(self):
        print "hello daughter"

    def goodbye(self):
        print "goodbye daughter"

if __name__ == '__main__':
    son = SonObject()
    son.greetings()
    son.hello_son()
    son.goodbye()
    daughter = DaughterObject()
    daughter.greetings()
    daughter.hello_daughter()
    daughter.goodbye()

Đầu ra là:

-smile_warmly - SonObject
greetings
-smile_warmly - SonObject
hello son
-smile_warmly - SonObject
goodbye son
-smile_warmly - DaughterObject
greetings
-smile_warmly - DaughterObject
hello daughter
-smile_warmly - DaughterObject
goodbye daughter

0
2017-12-31 02:18



Điều này sẽ gây ra vấn đề nếu bạn khởi tạo một lớp nhiều hơn một lần, vì nó sẽ quấn các phương thức đã được bọc lại một lần nữa. Ít nhất, bạn nên đặt một lá cờ trên mỗi lớp khi bạn quấn nó, để đánh dấu nó như đã được bọc. Sau đó, bạn có thể kiểm tra cờ ở đầu và bỏ qua gói nếu lớp đã được bọc. - BrenBarn
Hmm - Tôi nghĩ rằng tôi có thể đã hơi say. Ví dụ về đồ chơi của tôi được đưa ra ở đây cung cấp kết quả tôi muốn, nhưng mã thực sự của tôi, phức tạp hơn, khiến tôi trở nên hoang mang TypeError: 'NoneType' object is not callable tin nhắn và sự cố. - jononomo
Bạn có thể muốn đăng câu hỏi mới với vấn đề đó. - BrenBarn
Có - ngay cả bây giờ tôi đang tạo ra một ví dụ tốt đẹp để cô lập vấn đề. :) - jononomo
Đây là câu hỏi mới của tôi: stackoverflow.com/q/14097784/1701170 - jononomo