2011-08-06 36 views
21

metaclasses Khi thảo luận, the docs nhà nước:Sử dụng phương pháp __call__ của metaclass thay vì __new__?

Bạn có thể dĩ nhiên cũng ghi đè phương thức lớp khác (hoặc thêm phương pháp mới); ví dụ: xác định phương thức __call__() tùy chỉnh trong metaclass cho phép hành vi tùy chỉnh khi lớp được gọi, ví dụ: không phải luôn tạo một phiên bản mới.

Câu hỏi của tôi là: giả sử tôi muốn có hành vi tùy chỉnh khi lớp được gọi, ví dụ như bộ nhớ đệm thay vì tạo đối tượng mới. Tôi có thể làm điều này bằng cách ghi đè phương thức __new__ của lớp. Khi nào tôi muốn xác định một metaclass với __call__ thay thế? Cách tiếp cận này cho rằng điều gì không thể đạt được với __new__?

Trả lời

13

Câu trả lời trực tiếp câu hỏi của bạn là: khi bạn muốn làm hơn hơn là chỉ tùy chỉnh tạo Ví dụ, hoặc khi bạn muốn tách riêng gì lớp không từ cách nó được tạo ra.

Xem câu trả lời của tôi cho Creating a singleton in Python và thảo luận được liên kết.

Có một số lợi thế.

  1. Nó cho phép bạn tách riêng những gì lớp học thực hiện từ chi tiết về cách tạo. Lớp metaclass và lớp học đều chịu trách nhiệm cho một điều.

  2. Bạn có thể viết mã một lần trong metaclass và sử dụng mã này để tùy chỉnh hành vi gọi của một số lớp mà không phải lo lắng về đa kế thừa.

  3. Lớp con có thể ghi đè hành vi theo phương pháp __new__ của chúng, nhưng __call__ trên metaclass thậm chí không cần gọi số __new__.

  4. Nếu có công việc thiết lập, bạn có thể thực hiện theo phương pháp __new__ của metaclass và nó chỉ xảy ra một lần, thay vì mỗi lần lớp được gọi.

Có rất nhiều trường hợp tùy chỉnh __new__ cũng hoạt động tốt nếu bạn không lo lắng về nguyên tắc trách nhiệm duy nhất.

Nhưng có các trường hợp sử dụng khác phải xảy ra trước đó, khi lớp được tạo, thay vì khi cá thể được tạo. Đó là khi những người đến chơi rằng một metaclass là cần thiết. Xem What are your (concrete) use-cases for metaclasses in Python? để biết nhiều ví dụ tuyệt vời.

0

Đó là vấn đề của các giai đoạn vòng đời và những gì bạn có quyền truy cập. __call__ được gọi là sau__new__ và được chuyển thông số khởi tạo trước chúng được chuyển đến __init__, vì vậy bạn có thể thao tác chúng. Hãy thử mã này và nghiên cứu sản lượng của nó:

class Meta(type): 
    def __new__(cls, name, bases, newattrs): 
     print "new: %r %r %r %r" % (cls, name, bases, newattrs,) 
     return super(Meta, cls).__new__(cls, name, bases, newattrs) 

    def __call__(self, *args, **kw): 
     print "call: %r %r %r" % (self, args, kw) 
     return super(Meta, self).__call__(*args, **kw) 

class Foo: 
    __metaclass__ = Meta 

    def __init__(self, *args, **kw): 
     print "init: %r %r %r" % (self, args, kw) 

f = Foo('bar') 
print "main: %r" % f 
+1

Không! '__new__' trên metaclass xảy ra khi _class_ được tạo, không phải là _instance_.'__call__' xảy ra khi' __new__' xảy ra mà không có metaclass. – agf

+1

Tôi nói rằng '__new__' liên quan đến việc tạo ví dụ ở đâu? – pyroscope

+4

Tôi đã thực sự hỏi về '__new__' của lớp, không phải là' __new__' của metaclass. –

12

Một khác biệt là bằng cách định nghĩa một phương pháp metaclass __call__ bạn đang đòi hỏi rằng nó được gọi trước khi bất kỳ của lớp hoặc __new__ phương pháp phân lớp của được một cơ hội để được gọi.

class MetaFoo(type): 
    def __call__(cls,*args,**kwargs): 
     print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs)) 

class Foo(object): 
    __metaclass__=MetaFoo 

class SubFoo(Foo): 
    def __new__(self,*args,**kwargs): 
     # This never gets called 
     print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs)) 

sub=SubFoo() 
foo=Foo() 

# MetaFoo: <class '__main__.SubFoo'>,(),{} 
# MetaFoo: <class '__main__.Foo'>,(),{} 

Lưu ý rằng SubFoo.__new__ không bao giờ được gọi. Ngược lại, nếu bạn xác định Foo.__new__ mà không có metaclass, bạn cho phép các lớp con ghi đè Foo.__new__.

Tất nhiên, bạn có thể xác định MetaFoo.__call__ để gọi cls.__new__, nhưng điều đó tùy thuộc vào bạn. Bằng cách từ chối làm như vậy, bạn có thể ngăn các lớp con có phương thức __new__ được gọi.

Tôi không thấy lợi thế hấp dẫn khi sử dụng metaclass tại đây. Và vì "Đơn giản hơn là phức tạp", tôi khuyên bạn nên sử dụng __new__.

+0

Cũng lưu ý rằng 'cls .__ new __()' sẽ được gọi gián tiếp nếu phương thức 'MetaFoo .__ call __()' gọi 'super (MetaFoo, cls) .__ gọi __ (* args, ** kwargs)'. – martineau

5

Sự khác biệt tinh tế trở nên rõ hơn một chút khi bạn quan sát cẩn thận thứ tự thực hiện của các phương pháp này.

class Meta_1(type): 
    def __call__(cls, *a, **kw): 
     print "entering Meta_1.__call__()" 
     rv = super(Meta_1, cls).__call__(*a, **kw) 
     print "exiting Meta_1.__call__()" 
     return rv 

class Class_1(object): 
    __metaclass__ = Meta_1 
    def __new__(cls, *a, **kw): 
     print "entering Class_1.__new__()" 
     rv = super(Class_1, cls).__new__(cls, *a, **kw) 
     print "exiting Class_1.__new__()" 
     return rv 

    def __init__(self, *a, **kw): 
     print "executing Class_1.__init__()" 
     super(Class_1,self).__init__(*a, **kw) 

Lưu ý rằng đoạn mã trên không thực sự làm bất cứ điều gì khác hơn là đăng những gì chúng ta đang làm. Mỗi phương thức đều ngăn cản việc triển khai cha mẹ của nó, tức là phương thức mặc định của nó. Vì vậy, bên cạnh khai thác gỗ đó là một cách hiệu quả, nếu như bạn đã chỉ đơn giản tuyên bố điều như sau:

class Meta_1(type): pass 
class Class_1(object): 
    __metaclass__ = Meta_1 

Và bây giờ chúng ta hãy tạo một thể hiện của Class_1

c = Class_1() 
# entering Meta_1.__call__() 
# entering Class_1.__new__() 
# exiting Class_1.__new__() 
# executing Class_1.__init__() 
# exiting Meta_1.__call__() 

Vì vậy nếu type là phụ huynh của Meta_1 chúng ta có thể tưởng tượng một pseudo triển khai type.__call__() như vậy:

class type: 
    def __call__(cls, *args, **kwarg): 

     # ... a few things could possibly be done to cls here... maybe... or maybe not... 

     # then we call cls.__new__() to get a new object 
     obj = cls.__new__(cls, *args, **kwargs) 

     # ... a few things done to obj here... maybe... or not... 

     # then we call obj.__init__() 
     obj.__init__(*args, **kwargs) 

     # ... maybe a few more things done to obj here 

     # then we return obj 
     return obj 

Thông báo từ cuộc gọi orde r ở trên số Meta_1.__call__() (hoặc trong trường hợp này là type.__call__()) có cơ hội ảnh hưởng đến việc có hay không gọi đến số Class_1.__new__()Class_1.__init__() sau cùng được thực hiện. Trong quá trình thực hiện, Meta_1.__call__() có thể trả lại một đối tượng mà thậm chí không bị chạm vào. Lấy ví dụ phương pháp này để mô hình singleton:

class Meta_2(type): 
    __Class_2_singleton__ = None 
    def __call__(cls, *a, **kw): 
     # if the singleton isn't present, create and register it 
     if not Meta_2.__Class_2_singleton__: 
      print "entering Meta_2.__call__()" 
      Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw) 
      print "exiting Meta_2.__call__()" 
     else: 
      print ("Class_2 singleton returning from Meta_2.__call__(), " 
        "super(Meta_2, cls).__call__() skipped") 
     # return singleton instance 
     return Meta_2.__Class_2_singleton__ 

class Class_2(object): 
    __metaclass__ = Meta_2 
    def __new__(cls, *a, **kw): 
     print "entering Class_2.__new__()" 
     rv = super(Class_2, cls).__new__(cls, *a, **kw) 
     print "exiting Class_2.__new__()" 
     return rv 

    def __init__(self, *a, **kw): 
     print "executing Class_2.__init__()" 
     super(Class_2, self).__init__(*a, **kw) 

Hãy quan sát những gì xảy ra khi liên tục cố gắng để tạo ra một đối tượng kiểu Class_2

a = Class_2() 
# entering Meta_2.__call__() 
# entering Class_2.__new__() 
# exiting Class_2.__new__() 
# executing Class_2.__init__() 
# exiting Meta_2.__call__() 

b = Class_2() 
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped 

c = Class_2() 
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped 

print a is b is c 
True 

Bây giờ quan sát thực hiện này sử dụng một lớp __new__() phương pháp thử để thực hiện điều tương tự.

import random 
class Class_3(object): 

    __Class_3_singleton__ = None 

    def __new__(cls, *a, **kw): 
     # if singleton not present create and save it 
     if not Class_3.__Class_3_singleton__: 
      print "entering Class_3.__new__()" 
      Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw) 
      rv.random1 = random.random() 
      rv.random2 = random.random() 
      print "exiting Class_3.__new__()" 
     else: 
      print ("Class_3 singleton returning from Class_3.__new__(), " 
        "super(Class_3, cls).__new__() skipped") 

     return Class_3.__Class_3_singleton__ 

    def __init__(self, *a, **kw): 
     print "executing Class_3.__init__()" 
     print "random1 is still {random1}".format(random1=self.random1) 
     # unfortunately if self.__init__() has some property altering actions 
     # they will affect our singleton each time we try to create an instance 
     self.random2 = random.random() 
     print "random2 is now {random2}".format(random2=self.random2) 
     super(Class_3, self).__init__(*a, **kw) 

Chú ý rằng việc thực hiện ở trên mặc dù đăng ký thành công một singleton trên lớp, không ngăn cản __init__() khỏi bị gọi là, điều này xảy ra ngầm trong type.__call__() (type là metaclass mặc định nếu không chỉ định). Điều này có thể dẫn đến một số hiệu ứng không mong muốn:

a = Class_3() 
# entering Class_3.__new__() 
# exiting Class_3.__new__() 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.739298365475 

b = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.247361634396 

c = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.436144427555 

d = Class_3() 
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped 
# executing Class_3.__init__() 
# random1 is still 0.282724600824 
# random2 is now 0.167298405242 

print a is b is c is d 
# True 
Các vấn đề liên quan