2011-11-10 45 views
7

Lấy ví dụ tối thiểu sau đây:“Không thể khởi tạo lớp trừu tượng ... với các phương pháp trừu tượng” trên lớp học mà không cần phải có bất kỳ phương pháp trừu tượng

import abc 

class FooClass(object): 
    __metaclass__ = abc.ABCMeta 

    @abc.abstractmethod 
    def FooMethod(self): 
    raise NotImplementedError() 


def main(): 
    derived_type = type('Derived', (FooClass,), {}) 

    def BarOverride(self): 
    print 'Hello, world!' 
    derived_type.FooMethod = BarOverride 

    instance = derived_type() 

Chạy main() giúp bạn:

TypeError: Can't instantiate abstract class Derived with abstract methods FooMethod 

(Ngoại lệ xảy ra trên dòng instance = derived_type().)

Nhưng FooMethod không được trừu tượng: Tôi đã ghi đè nó bằng BarOverride. Vậy tại sao ngoại lệ này lại tăng lên?

Tuyên bố từ chối trách nhiệm: Có, tôi có thể sử dụng cú pháp rõ ràng class và thực hiện chính xác điều tương tự. (Và thậm chí tốt hơn, tôi có thể làm cho nó hoạt động!) Nhưng đây là một trường hợp thử nghiệm tối thiểu, và ví dụ lớn hơn là tự động tạo các lớp. :-) Và tôi tò mò là tại sao điều này không hoạt động.

Edit: Và để ngăn chặn sự rõ ràng không phải là câu trả lời khác: Tôi không muốn vượt qua BarOverride trong đối số thứ ba để type: Trong ví dụ thực tế, BarOverride cần có derived_type ràng buộc với nó. Sẽ dễ dàng hơn nếu tôi có thể xác định BarOverride sau khi tạo derived_type. (Nếu tôi không thể làm được điều này, thì tại sao?)

+1

Các tính trừu tượng của một lớp được xác định trong quá trình thi lớp . Tại sao bạn không đơn giản bao gồm 'FooMethod' trong từ điển trong khi tạo lớp? –

+0

@SvenMarnach: 'BarOverride', trong ví dụ thực, cần phải có lớp dẫn xuất ràng buộc với nó. Tạo hàm sau đó là cách dễ nhất để thực hiện việc này. – Thanatos

+0

Tôi không hiểu điểm đó. Bạn có ý nghĩa gì bởi "cần phải có lớp dẫn xuất ràng buộc với nó"? –

Trả lời

4

Because the docs say so:

động thêm các phương pháp trừu tượng đến một lớp học, hoặc cố gắng thay đổi trạng thái trừu tượng của một phương pháp hoặc lớp một khi nó được tạo ra, không được hỗ trợ. Abstractmethod() chỉ ảnh hưởng đến các lớp con bắt nguồn bằng cách sử dụng thừa kế thông thường; “Các lớp con ảo” đã đăng ký với phương thức register() của ABC không bị ảnh hưởng.

Một metaclass chỉ được gọi khi một lớp được xác định. Khi abstractmethod đã đánh dấu một lớp là trừu tượng thì trạng thái đó sẽ không thay đổi sau này.

+0

Chấp nhận điều này làm câu trả lời (vì nó chứa lý do mã của tôi không hoạt động), nhưng cả ba đều thú vị. Tôi đã chọn câu trả lời của unutbu là giải pháp cho vấn đề trong tầm tay. – Thanatos

2

Vâng, nếu bạn phải thực hiện theo cách này, thì bạn chỉ có thể chuyển dict dummy {'FooMethod':None} làm đối số thứ ba để nhập. Điều này cho phép derived_type để đáp ứng yêu cầu của ABCMeta rằng tất cả các phương thức trừu tượng đều bị ghi đè. Sau đó, bạn có thể cung cấp số thực FooMethod:

def main(): 
    derived_type = type('Derived', (FooClass,), {'FooMethod':None}) 
    def BarOverride(self): 
    print 'Hello, world!' 
    setattr(derived_type, 'FooMethod', BarOverride) 
    instance = derived_type() 
+0

Nhưng điều này đánh bại một số điểm làm cho trừu tượng 'FooMethod', bởi vì nếu việc tạo kiểu và' setattr' được tách ra, bạn có thể khởi tạo 'derived_type' trước khi thiết lập phương thức. – agf

+0

Một loại lớp 'ABCMeta' phải có khả năng vá khỉ giống như bất kỳ lớp nào khác. Bằng cách cung cấp đối số thứ ba, bạn thừa nhận rằng 'Derived' phải định nghĩa' FooMethod'. Điều đó không ngăn cản bạn thay đổi ý định về việc triển khai sau này. – unutbu

3

Jochen là đúng; các phương thức trừu tượng được thiết lập khi tạo lớp và sẽ không sửa đổi tôi chỉ vì bạn gán lại một thuộc tính.

Bạn có thể tự loại bỏ nó khỏi danh sách các phương pháp trừu tượng bằng cách làm

DerivedType.__abstractmethods__ = frozenset() 

hoặc

DerivedType.__abstractmethods__ = frozenset(
     elem for elem in DerivedType.__abstractmethods__ if elem != 'FooMethod') 

cũng như setattr, vì vậy nó không vẫn nghĩ rằng FooMethod là trừu tượng.

3

Tôi biết chủ đề này thực sự cũ nhưng ... Đó thực sự là một câu hỏi hay.

Nó không hoạt động vì abc chỉ có thể kiểm tra các phương pháp trừu tượng trong quá trình instatiation của các loại, có nghĩa là, khi type('Derived', (FooClass,), {}) đang chạy. Bất kỳ setattr được thực hiện sau đó là không thể truy cập từ abc.

Vì vậy, công việc sẽ không setattr, buuut ... Vấn đề của bạn giải quyết tên của một lớp học mà trước đây không công bố hoặc định nghĩa trông thể giải quyết được:

Tôi đã viết một metaclass nhỏ mà cho phép bạn sử dụng một placeholder "clazz" để truy cập vào bất kỳ lớp nào mà cuối cùng sẽ nhận được phương thức bạn đang viết bên ngoài một định nghĩa lớp.

Bằng cách đó bạn sẽ không nhận được TypeError từ abc nữa, vì bây giờ bạn có thể xác định phương thức của bạn TRƯỚC KHI instatiating loại của bạn, và sau đó vượt qua nó để gõ vào đối số dict. Sau đó, abc sẽ xem nó như là một phương pháp ghi đè thích hợp.

Aaand, với metaclass mới, bạn có thể tham chiếu đến đối tượng lớp trong phương thức đó. Và đây là siêu, bởi vì bây giờ bạn có thể sử dụng siêu! = P tôi có thể đoán bạn đang lo lắng về điều đó quá ...

Hãy xem:

import abc 
import inspect 

clazz = type('clazz', (object,), {})() 

def clazzRef(func_obj): 
    func_obj.__hasclazzref__ = True 
    return func_obj 

class MetaClazzRef(type): 
    """Makes the clazz placeholder work. 

    Checks which of your functions or methods use the decorator clazzRef 
    and swaps its global reference so that "clazz" resolves to the 
    desired class, that is, the one where the method is set or defined. 

    """ 
    methods = {} 
    def __new__(mcs, name, bases, dict): 
     ret = super(MetaClazzRef, mcs).__new__(mcs, name, bases, dict) 
     for (k,f) in dict.items(): 
      if getattr(f, '__hasclazzref__', False): 
       if inspect.ismethod(f): 
        f = f.im_func 
       if inspect.isfunction(f): 
        for (var,value) in f.func_globals.items(): 
         if value is clazz: 
          f.func_globals[var] = ret 
     return ret 

class MetaMix(abc.ABCMeta, MetaClazzRef): 
    pass 

class FooClass(object): 
    __metaclass__ = MetaMix 
    @abc.abstractmethod 
    def FooMethod(self): 
     print 'Ooops...' 
     #raise NotImplementedError() 


def main(): 
    @clazzRef 
    def BarOverride(self): 
     print "Hello, world! I'm a %s but this method is from class %s!" % (type(self), clazz) 
     super(clazz, self).FooMethod() # Now I have SUPER!!! 

    derived_type = type('Derived', (FooClass,), {'FooMethod': BarOverride}) 

    instance = derived_type() 
    instance.FooMethod() 

    class derivedDerived(derived_type): 
     def FooMethod(self): 
      print 'I inherit from derived.' 
      super(derivedDerived,self).FooMethod() 

    instance = derivedDerived() 
    instance.FooMethod() 

main() 

Đầu ra là:

Hello, world! I'm a <class 'clazz.Derived'> but this method is from class <class 'clazz.Derived'>! 
Ooops... 
I inherit from derived. 
Hello, world! I'm a <class 'clazz.derivedDerived'> but this method is from class <class 'clazz.Derived'>! 
Ooops... 
+0

Tôi đoán điều này là sai. Tôi nên làm điều này với đóng cửa của chức năng, không phải globals(). Globals() là, err, global ... Nhưng, kỳ lạ, mã có vẻ hoạt động như mong đợi, thậm chí với nhiều lớp hơn ... – rbertoche

Các vấn đề liên quan