2013-08-08 25 views
60

Có cách nào để kích hoạt mã khi lớp của tôi được phân lớp không?Python: Chạy mã khi một lớp được phân loại là

class SuperClass: 
    def triggered_routine(subclass): 
     print("was subclassed by " + subclass.__name__) 

magically_register_triggered_routine() 

print("foo") 

class SubClass0(SuperClass): 
    pass 

print("bar") 

class SubClass1(SuperClass): 
    print("test") 

đầu ra

foo 
was subclassed by SubClass0 
bar 
test 
was subclassed by SubClass1 
+1

Sử dụng metaclass; metaclasses được gọi khi các lớp được tạo ra, giống như các lớp được gọi khi các cá thể được tạo ra. –

+2

Không thể thêm câu trả lời, nhưng hôm nay python3.6 có '__init_subclass__' - hãy kiểm tra nó! –

+1

@OrDuan: cảm ơn, âm thanh hữu ích. Thậm chí có thể là lý do đủ để bỏ đánh dấu câu hỏi này một bản sao, vì bây giờ có một giải pháp dành riêng cho vấn đề của tôi thay vì "sử dụng một metaclass" một. –

Trả lời

39

Lớp học (theo mặc định) nên là trường hợp của type. Giống như một thể hiện của một lớp Foo được tạo bởi foo = Foo(...), một thể hiện của type (tức là một lớp) được tạo bởi myclass = type(name, bases, clsdict).

Nếu bạn muốn điều gì đó đặc biệt xảy ra tại thời điểm tạo lớp học, thì bạn phải sửa đổi điều tạo lớp học - tức là type. Cách để làm điều đó là xác định một lớp con của type - tức là một metaclass.

Một metaclass là lớp của nó như là một lớp là ví dụ của nó.

Trong python2 bạn sẽ xác định các metaclass của một lớp học với

class SuperClass: 
    __metaclass__ = Watcher 

nơi Watcher là một lớp con của type.

Trong Python3 cú pháp đã được thay đổi để

class SuperClass(metaclass=Watcher) 

Cả hai đều tương đương với

Superclass = Watcher(name, bases, clsdict) 

nơi trong trường hợp này, name bằng chuỗi 'Superclass', và bases là tuple (object,). clsdict là một từ điển thuộc tính lớp được định nghĩa trong phần thân của định nghĩa lớp.

Lưu ý sự giống nhau với myclass = type(name, bases, clsdict).

Vì vậy, cũng giống như bạn sẽ sử dụng một __init__ của lớp để kiểm soát các sự kiện tại thời điểm tạo ra một ví dụ, bạn có thể kiểm soát các sự kiện tại thời điểm tạo ra một lớp với một metaclass __init__:


class Watcher(type): 
    def __init__(cls, name, bases, clsdict): 
     if len(cls.mro()) > 2: 
      print("was subclassed by " + name) 
     super(Watcher, cls).__init__(name, bases, clsdict) 

class SuperClass: 
    __metaclass__ = Watcher 


print("foo") 

class SubClass0(SuperClass): 
    pass 

print("bar") 

class SubClass1(SuperClass): 
    print("test") 

in

foo 
was subclassed by SubClass0 
bar 
test 
was subclassed by SubClass1 
+2

Cảm ơn. Tuy nhiên, mã của bạn chỉ hoạt động trong python2. Trong python3, SuperClass sẽ cần phải là 'lớp SuperClass (metaclass = Watcher): pass' –

+0

Cảm ơn. Tôi đã chỉnh sửa bài đăng để giải quyết sự khác biệt trong cú pháp Python2 và Python3. – unutbu

+1

Hai điều quan trọng mà tôi đã bỏ lỡ khi tôi tự mình thử nghiệm: 1 - 'Các lớp con' Watch', không phải 'đối tượng'. 2 - 'SuperClass' không có siêu lớp nào cả, không phải' đối tượng'. Tôi chỉ được sử dụng để viết xuống 'đối tượng' như là một siêu lớp cho mỗi lớp tôi xác định rằng tôi bằng cách nào đó đã bỏ lỡ nó rằng mã này đã được sử dụng cái gì khác. Mặc dù dường như không có bất kỳ tác hại nào trong việc tạo ra 'SuperClass' kế thừa từ' đối tượng' ... nó chắc chắn cần thiết cho 'Watcher' kế thừa từ' type', không phải 'đối tượng'. – ArtOfWarfare

6

Chỉnh sửa: Bài đăng cũ của tôi thực sự không hoạt động. Phân lớp từ classmethod không hoạt động như mong đợi. Trước tiên, chúng tôi muốn có một số cách để nói cho metaclass rằng phương pháp cụ thể này được cho là có đặc biệt được gọi là hành vi phân lớp, chúng tôi sẽ chỉ thiết lập một thuộc tính trên hàm mà chúng ta muốn gọi. Quay trở lại đầu trang Để thuận tiện, chúng tôi thậm chí sẽ biến chức năng thành một số classmethod để có thể phát hiện ra chiếc đồng hồ cát thực sự được tìm thấy trong đó. Chúng ta sẽ trả về classmethod để nó có thể được sử dụng như một trang trí, đó là thuận tiện nhất.

import types 
import inspect 

def subclass_hook(func): 
    func.is_subclass_hook = True 
    return classmethod(func) 

Chúng tôi cũng sẽ muốn có cách thuận tiện để thấy rằng trang trí subclass_hook được sử dụng. Chúng tôi biết rằng classmethod đã được sử dụng, vì vậy, chúng tôi sẽ kiểm tra điều đó và chỉ tìm kiếm thuộc tính is_subclass_hook.

def test_subclass_hook(thing): 
    x = (isinstance(thing, types.MethodType) and 
     getattr(thing.im_func, 'is_subclass_hook', False)) 
    return x 

Cuối cùng, chúng ta cần một metaclass hoạt động trên thông tin: Đối với hầu hết các trường hợp, điều thú vị nhất cần thực hiện ở đây là kiểm tra từng cơ sở được cung cấp cho móc. Bằng cách đó, siêu hoạt động theo cách ít ngạc nhiên nhất.

class MyMetaclass(type): 
    def __init__(cls, name, bases, attrs): 
     super(MyMetaclass, cls).__init__(name, bases, attrs) 

     for base in bases: 
      if base is object: 
       continue 
      for name, hook in inspect.getmembers(base, test_subclass_hook): 
       hook(cls) 

và điều đó nên thực hiện.

>>> class SuperClass: 
...  __metaclass__ = MyMetaclass 
...  @subclass_hook 
...  def triggered_routine(cls, subclass): 
...   print(cls.__name__ + " was subclassed by " + subclass.__name__) 

>>> class SubClass0(SuperClass): 
...  pass 
SuperClass was subclassed by SubClass0 

>>> class SubClass1(SuperClass): 
...  print("test") 
test 
SuperClass was subclassed by SubClass1 
Các vấn đề liên quan