2017-11-04 16 views
6

Có thể sửa đổi một lớp học để làm cho có sẵn một phương pháp trang trí nhất định, mà không cần phải nhập nó một cách rõ ràng và không có tiền tố nó (@something.some_decorator):Làm cách nào để trang trí mới có sẵn trong một lớp học mà không cần nhập chúng một cách rõ ràng?

class SomeClass: 

    @some_decorator 
    def some_method(self): 
     pass 

Tôi không nghĩ rằng đây có thể với một trang trí lớp, bởi vì điều đó được áp dụng quá muộn. Các tùy chọn có vẻ hứa hẹn hơn là sử dụng một metaclass, nhưng tôi không chắc chắn như thế nào, tôi đoán là tôi sẽ phải có some_decorator được giới thiệu vào không gian tên của SomeClass.

Nhờ @MartijnPieters để chỉ ra rằng staticmethodclassmethod được tích hợp sẵn. Tôi đã mong đợi họ là một phần của máy móc type.

Để rõ ràng, tôi không có bất kỳ trường hợp sử dụng rõ ràng nào cho điều này, tôi chỉ tò mò xem liệu điều này có thể thực hiện được hay không.

THÊM, bây giờ câu hỏi đã được trả lời. Lý do ban đầu tại sao tôi đang tìm cách vượt ra ngoài việc nhập hoặc xác định một trang trí cục bộ, là tôi đã định nghĩa một trình trang trí sẽ chỉ hoạt động nếu một thuộc tính vùng chứa nào đó được khởi tạo trên một đối tượng và tôi đang tìm cách thực thi việc này sự sẵn có của trang trí. Tôi đã kết thúc kiểm tra xem thuộc tính có tồn tại không và nếu không khởi tạo nó trong trang trí, điều này rất có thể là cái ác nhỏ hơn ở đây.

+1

Cảm ơn bạn đã cập nhật điều này, câu hỏi * bây giờ rõ ràng hơn so với trước đây. –

Trả lời

7

Có, trong Python 3 bạn có thể sử dụng metaclass __prepare__ hook. Nó được dự kiến ​​sẽ trả về một bản đồ, và nó là cơ sở của không gian tên địa phương cho cơ thể lớp:

def some_decorator(f): 
    print(f'Decorating {f.__name__}') 
    return f 

class meta(type): 
    @classmethod 
    def __prepare__(mcls, name, bases, **kw): 
     return {'some_decorator': some_decorator} 

class SomeClass(metaclass=meta): 
    @some_decorator 
    def some_method(self): 
     pass 

Chạy trên sản xuất

Decorating some_method 

Tuy nhiên, bạn không nên sử dụng này. Vì trạng thái Zen of Python: Rõ ràng là tốt hơn là ẩn và giới thiệu tên ma thuật vào các lớp học của bạn có thể dễ dàng dẫn đến nhầm lẫn và lỗi. Việc nhập một metaclass không khác gì so với việc nhập trang trí, bạn đã thay thế một tên bằng một trang khác.

Trình trang trí lớp học vẫn có thể áp dụng các trang trí khác cho các phương thức trên lớp sau khi lớp học đã được tạo. Cú pháp @decorator chỉ là cú pháp đường cho name = decorator(decorated_object), bạn luôn có thể áp dụng một trang trí sau này bằng cách sử dụng name = decorator(name) hoặc trong ngữ cảnh lớp, như cls.name = decorator(cls.name). Nếu bạn cần chọn và chọn phương thức nào áp dụng, bạn có thể chọn tiêu chí như tên phương thức hoặc thuộc tính được đặt trên phương thức hoặc chuỗi tài liệu của phương thức, v.v. Hoặc chỉ sử dụng trang trí trực tiếp trên phương pháp.

+0

Tôi vừa chạy thử nghiệm. Các tên trong vùng tên đã chuẩn bị (__prepare__) thực sự có sẵn trong lớp. Vì vậy, một metaclass có thể làm điều này. –

+0

@SamHartman: Tôi cũng đã chạy thử nghiệm, nhưng bây giờ tôi đã gặp lỗi. Sẽ kiểm tra lại. –

+0

@SamHartman: ugh, typo trong metaclass tôi đã thực hiện. –

3

Tốt nhất tôi có thể nói một metaclass có thể làm điều này. Bạn nào đó cần phải nhận được trang trí sẵn sàng cho metaclass, có thể bằng cách nhập khẩu, và sau đó bạn có thể bao gồm chúng trong không gian tên chuẩn bị:

class includewraps(type): 
    def prepare(*args): 
     from functools import wraps 
     return {'wraps': wraps} 


class haswraps (metaclass = includewraps): 
    # wraps is available in this scope 
2

Viết trang trí mà phải mất một danh sách các chuỗi và nhập khẩu chúng trước khi chức năng là được gọi lần đầu tiên. Điều này tránh được việc nhập khẩu rõ ràng cho đến giây phút cuối cùng có thể trước khi chúng cần thiết. Điều này, giống như tất cả các câu trả lời khác ở đây, có lẽ là một dấu hiệu cho thấy mã của bạn nên được cấu trúc lại thay thế.

from functools import wraps 
from importlib import import_module 

def deferred(*names): 
    def decorator(f): 
     # this will hold the fully decorated function 
     final_f = None 

     @wraps(f) 
     def wrapper(*args, **kwargs): 
      nonlocal final_f 

      if final_f is None: 
       # start with the initial function 
       final_f = f 

       for name in names: 
        # assume the last . is the object to import 
        # import the module then get the object 
        mod, obj = name.rsplit('.', 1) 
        d = getattr(import_module(mod), obj) 
        # decorate the function and keep going 
        final_f = d(final_f) 

      return final_f(*args, **kwargs) 

     return wrapper 

    return decorator 
# for demonstration purposes, decorate with a function defined after this 
# assumes this file is called "example.py" 
@deferred('example.double') 
def add(x, y): 
    return x + y 

def double(f): 
    @wraps(f) 
    def wrapper(*args, **kwargs): 
     return 2 * f(*args, **kwargs) 

    return wrapper 

if __name__ == '__main__': 
    print(add(3, 6)) # 18 

Những lập luận để deferred nên chuỗi có dạng 'path.to.module.decorator. path.to.module được nhập và sau đó decorator được truy xuất từ ​​mô-đun. Mỗi trang trí được áp dụng để bọc chức năng. Chức năng được lưu trữ trong một nonlocal để nhập và trang trí này chỉ cần xảy ra lần đầu tiên chức năng được gọi.

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