Một số sử dụng phổ biến cho trang trí Python là gì? [đóng cửa]


335

Trong khi tôi muốn nghĩ về bản thân mình như một lập trình viên Python có năng lực hợp lý, một khía cạnh của ngôn ngữ mà tôi chưa bao giờ có thể mò mẫm là trang trí.

Tôi biết chúng là gì (bề ngoài), tôi đã đọc hướng dẫn, ví dụ, câu hỏi về Stack Overflow và tôi hiểu cú pháp, có thể tự viết, thỉnh thoảng sử dụng @ classmethod và @staticmethod, nhưng tôi không bao giờ sử dụng trang trí để giải quyết một vấn đề trong mã Python của riêng tôi. Tôi không bao giờ gặp phải một vấn đề mà tôi nghĩ, "Hmm ... đây có vẻ như là một công việc cho một người trang trí!"

Vì vậy, tôi tự hỏi nếu các bạn có thể đưa ra một số ví dụ về nơi bạn đã sử dụng trang trí trong các chương trình của riêng mình và hy vọng tôi sẽ có một "A-ha!" Khoảnh khắc và có được chúng.


5
Ngoài ra, các trình trang trí rất hữu ích cho Ghi nhớ - đó là lưu trữ kết quả chậm tính toán của một hàm. Trình trang trí có thể trả về một hàm kiểm tra các đầu vào và nếu chúng đã được trình bày, trả về một kết quả được lưu trữ.
Peter

1
Lưu ý rằng Python có một công cụ trang trí tích hợp, functools.lru_cachehoạt động chính xác như những gì Peter đã nói, kể từ Python 3.2, được phát hành vào tháng 2 năm 2011
Taegyung

Nội dung của Thư viện trang trí Python sẽ cho bạn ý tưởng tốt về các mục đích sử dụng khác cho chúng.
martineau

Câu trả lời:


126

Tôi sử dụng trang trí chủ yếu cho mục đích thời gian

def time_dec(func):

  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res

  return wrapper


@time_dec
def myFunction(n):
    ...

13
Trong Unix, time.clock()đo thời gian CPU. Bạn có thể muốn sử dụng time.time()thay thế nếu bạn muốn đo thời gian đồng hồ treo tường.
Jabba

20
Ví dụ tuyệt vời! Không biết nó làm gì mặc dù. Một lời giải thích những gì bạn đang làm ở đó, và cách người trang trí giải quyết vấn đề sẽ rất tốt đẹp.
MeLight

7
Chà, nó đo thời gian cần thiết myFunctionđể chạy ...
RS.us

98

Tôi đã sử dụng chúng để đồng bộ hóa.

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return newFunction
    return wrap

Như đã chỉ ra trong các nhận xét, vì Python 2.5, bạn có thể sử dụng một withcâu lệnh kết hợp với một đối tượng threading.Lock(hoặc multiprocessing.Lockkể từ phiên bản 2.6) để đơn giản hóa việc triển khai của trình trang trí thành:

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            with lock:
                return f(*args, **kw)
        return newFunction
    return wrap

Bất kể, sau đó bạn sử dụng nó như thế này:

import threading
lock = threading.Lock()

@synchronized(lock)
def do_something():
  # etc

@synchronzied(lock)
def do_something_else():
  # etc

Về cơ bản, nó chỉ đặt lock.acquire()/ lock.release()ở hai bên của lệnh gọi hàm.


17
Có thể hợp lý, nhưng trang trí vốn đã khó hiểu, đặc biệt. đến những người đầu tiên năm sau, những người đến sau bạn và cố gắng sửa đổi mã của bạn. Tránh điều này một cách đơn giản: chỉ cần do_s Something () gửi mã của nó trong một khối bên dưới 'có khóa:' và mọi người đều có thể thấy rõ mục đích của bạn. Các nhà trang trí được sử dụng quá mức bởi những người muốn có vẻ thông minh (và thực tế là nhiều) nhưng sau đó, mã đến với những người bình thường và bị ảnh hưởng.
Kevin J. Rice

18
@ KevinJ.Rice Ràng buộc mã của bạn để 'noobs năm đầu tiên' có thể hiểu rõ hơn đó là thực tế khủng khiếp. Cú pháp trang trí dễ đọc hơn rất nhiều và tách mã rất nhiều.
TaylerJones 4/2/2015

18
@TaylerJones, khả năng đọc mã chỉ là ưu tiên cao nhất của tôi khi viết. Mã được đọc hơn 7 lần cho mỗi lần sửa đổi. Mã khó hiểu (đối với noobs hoặc cho các chuyên gia đang làm việc dưới áp lực thời gian) là khoản nợ kỹ thuật phải được thanh toán mỗi khi có ai đó truy cập vào cây nguồn.
Kevin J. Rice

@TaylerJones Một trong những nhiệm vụ quan trọng nhất đối với một lập trình viên là cung cấp sự rõ ràng.
JDOaktown

70

Tôi sử dụng các trình trang trí để kiểm tra các tham số kiểu được truyền cho các phương thức Python của tôi thông qua một số RMI. Vì vậy, thay vì lặp đi lặp lại việc đếm tham số tương tự, ngoại lệ - tăng mumbo-jumbo nhiều lần.

Ví dụ: thay vì:

def myMethod(ID, name):
    if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
        raise BlaBlaException() ...

Tôi chỉ tuyên bố:

@accepts(uint, utf8string)
def myMethod(ID, name):
    ...

accepts()làm tất cả công việc cho tôi.


15
Đối với bất kỳ ai quan tâm, có một triển khai @acceptstrong PEP 318.
martineau

2
Tôi nghĩ rằng có lỗi đánh máy .. phương pháp đầu tiên nên được chấp nhận .. bạn đã khai báo cả hai là "myMethod"
DevC

1
@DevC Không, nó không giống như một lỗi đánh máy. Vì đó rõ ràng không phải là việc thực hiện "chấp nhận (..)" và ở đây "chấp nhận (..)" thực hiện công việc mà hai dòng sẽ bắt đầu bằng "myMethod (..)" - đó là chỉ giải thích phù hợp.
Evgeni Sergeev

1
Xin lỗi về vết sưng, tôi chỉ muốn chỉ ra rằng việc kiểm tra loại đối số được truyền và nâng TypeError nếu không được coi là một thực tiễn xấu vì nó sẽ không chấp nhận ví dụ như int nếu nó chỉ kiểm tra nổi và vì thông thường bản thân mã phải thích ứng với các loại giá trị khác nhau được thông qua để linh hoạt tối đa.
Gustavo6046

2
Cách được đề xuất để thực hiện kiểm tra kiểu trong Python là thông qua isinstance()chức năng tích hợp sẵn, như đã được thực hiện trong triển khai PEP 318 của trình trang trí. Vì classinfođối số của nó có thể là một hoặc nhiều loại, sử dụng nó cũng sẽ giảm bớt sự phản đối (hợp lệ) của @ Gustavo6046. Python cũng có một Numberlớp cơ sở trừu tượng, vì vậy các thử nghiệm rất chung chung isinstance(42, numbers.Number)là có thể.
martineau

48

Trang trí được sử dụng cho bất cứ điều gì bạn muốn trong suốt "bọc" với chức năng bổ sung.

Django sử dụng chúng để gói chức năng "yêu cầu đăng nhập" trên các chức năng xem , cũng như để đăng ký các chức năng lọc .

Bạn có thể sử dụng trang trí lớp để thêm nhật ký được đặt tên vào các lớp .

Bất kỳ chức năng đủ chung chung nào mà bạn có thể "giải quyết" đối với một lớp hoặc hành vi của chức năng hiện có là trò chơi công bằng để trang trí.

Ngoài ra còn có một cuộc thảo luận về các trường hợp sử dụng trên nhóm tin Python-Dev được chỉ ra bởi PEP 318 - Trình trang trí cho các hàm và phương thức .


Cherrypy sử dụng @ cherrypy.expose để giữ thẳng các chức năng nào là công khai và các chức năng ẩn. Đó là lời giới thiệu đầu tiên của tôi và tôi đã quen với nó từ đó.
Marc Maxmeister

26

Đối với nosetests, bạn có thể viết một trình trang trí cung cấp một hàm hoặc phương thức kiểm tra đơn vị với một vài bộ tham số:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected

23

Thư viện Twisted sử dụng các bộ trang trí kết hợp với các bộ tạo để tạo ảo giác rằng một hàm không đồng bộ là đồng bộ. Ví dụ:

@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

Sử dụng điều này, mã sẽ được chia thành một tấn các hàm gọi lại nhỏ có thể được viết khá tự nhiên dưới dạng một khối, làm cho nó dễ hiểu và dễ bảo trì hơn rất nhiều.


14

Tất nhiên, một cách sử dụng rõ ràng là ghi nhật ký:

import functools

def log(logger, level='info'):
    def log_decorator(fn):
        @functools.wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
    for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()

10

Tôi sử dụng chúng chủ yếu để gỡ lỗi (trình bao quanh một chức năng in các đối số và kết quả của nó) và xác minh (ví dụ: để kiểm tra xem một đối số có đúng loại hay không, trong trường hợp ứng dụng web, nếu người dùng có đủ đặc quyền để gọi một cụ thể phương pháp).


6

Tôi đang sử dụng trang trí sau đây để làm cho một chủ đề chức năng an toàn. Nó làm cho mã dễ đọc hơn. Nó gần giống với cái được đề xuất bởi John Fouhy nhưng điểm khác biệt là một cái hoạt động trên một chức năng duy nhất và không cần phải tạo một đối tượng khóa rõ ràng.

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var

1
Điều này có nghĩa là mỗi chức năng, được trang trí, có khóa riêng của nó?
đau buồn

1
@grieve có, mỗi khi trình trang trí được sử dụng (được gọi), nó tạo ra một đối tượng khóa mới cho chức năng / phương thức được trang trí.
martineau

5
Điều đó thực sự nguy hiểm. Phương thức inc_var () là "threadafe" theo đó chỉ một người có thể gọi nó tại một thời điểm. Điều đó nói rằng, vì phương thức hoạt động trên biến thành viên "var" và có lẽ các phương thức khác cũng có thể hoạt động trên biến thành viên "var" và những truy cập đó không phải là chủ đề an toàn do khóa không được chia sẻ. Làm mọi thứ theo cách này mang lại cho người dùng của lớp X một cảm giác an toàn sai lầm.
Bob Van Zant

Đó không phải là chủ đề an toàn cho đến khi khóa duy nhất được sử dụng.
Chandu

5

Các công cụ trang trí được sử dụng hoặc để xác định các thuộc tính của hàm hoặc dưới dạng nồi hơi làm thay đổi nó; điều đó là có thể nhưng phản trực giác để chúng trả về các chức năng hoàn toàn khác nhau. Nhìn vào các phản hồi khác ở đây, có vẻ như một trong những cách sử dụng phổ biến nhất là giới hạn phạm vi của một số quy trình khác - có thể là ghi nhật ký, định hình, kiểm tra bảo mật, v.v.

CherryPy sử dụng việc gửi đối tượng để khớp URL với các đối tượng và cuối cùng là các phương thức. Các nhà trang trí trên các phương thức đó báo hiệu cho dù CherryPy có được phép sử dụng các phương thức đó hay không. Ví dụ, điều chỉnh từ hướng dẫn :

class HelloWorld:

    ...

    def secret(self):
        return "You shouldn't be here."

    @cherrypy.expose
    def index(self):
        return "Hello world!"

cherrypy.quickstart(HelloWorld())

Đây không phải là sự thật. Một trang trí có thể thay đổi hoàn toàn hành vi của một chức năng.
đệ quy

Được chứ. Nhưng bao lâu thì một người trang trí "thay đổi hoàn toàn hành vi của một chức năng?" Từ những gì tôi đã thấy, khi chúng không được sử dụng để chỉ định các thuộc tính, chúng chỉ được sử dụng cho mã soạn sẵn. Tôi đã chỉnh sửa phản hồi của mình.
Nikhil Chelliah

5

Tôi đã sử dụng chúng gần đây, trong khi làm việc trên ứng dụng web mạng xã hội. Đối với Cộng đồng / Nhóm, tôi được cho là ủy quyền thành viên để tạo cuộc thảo luận mới và trả lời tin nhắn mà bạn phải là thành viên của nhóm cụ thể đó. Vì vậy, tôi đã viết một trang trí @membership_requiredvà đặt nơi mà tôi yêu cầu trong quan điểm của tôi.


1

Tôi sử dụng trang trí này để sửa tham số

def fill_it(arg):
    if isinstance(arg, int):
        return "wan" + str(arg)
    else:
        try:
            # number present as string
            if str(int(arg)) == arg:
                return "wan" + arg
            else:
                # This should never happened
                raise Exception("I dont know this " + arg)
                print "What arg?"
        except ValueError, e:
            return arg

def fill_wanname(func):
    def wrapper(arg):
        filled = fill_it(arg)
        return func(filled)
    return wrapper

@fill_wanname
def get_iface_of(wanname):
    global __iface_config__
    return __iface_config__[wanname]['iface']

điều này được viết khi tôi cấu trúc lại một số hàm cần truyền đối số "wanN" nhưng trong các mã cũ của tôi, tôi chỉ truyền N hoặc 'N'


1

Trang trí có thể được sử dụng để dễ dàng tạo các biến phương thức hàm.

def static_var(varname, value):
    '''
    Decorator to create a static variable for the specified function
    @param varname: static variable name
    @param value: initial value for the variable
    '''
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("count", 0)
def mainCallCount():
    mainCallCount.count += 1

6
Cảm ơn bạn vì ví dụ của bạn, nhưng (apolgies) Tôi phải nói WTF - Tại sao bạn lại sử dụng cái này? Nó có tiềm năng lớn cho những người khó hiểu. Tất nhiên, tôi tôn trọng các nhu cầu cho việc sử dụng trường hợp cạnh, nhưng bạn đang gặp phải một vấn đề phổ biến mà nhiều nhà phát triển Python thiếu kinh nghiệm - không sử dụng đủ các lớp. Đó là, chỉ cần có một lớp var đếm đơn giản, khởi tạo nó và sử dụng nó. Noobs có xu hướng viết drop-thru (mã không dựa trên lớp) và cố gắng đối phó với việc thiếu chức năng lớp với các cách giải quyết phức tạp. Làm ơn đừng Xin vui lòng? xin lỗi để harp, cảm ơn bạn vì câu trả lời của bạn, nhưng bạn đã nhấn nút nóng cho tôi.
Kevin J. Rice

Tôi sẽ là -1 về điều này nếu nó xuất hiện như một yêu cầu kéo để tôi viết mã đánh giá, và vì vậy tôi cũng -1 về điều này như một con trăn tốt.
Techdragon

Dễ thương. Ngớ ngẩn, nhưng dễ thương. :) Tôi không bận tâm đến thuộc tính hàm thỉnh thoảng, nhưng chúng là một thứ hiếm gặp trong mã Python điển hình mà nếu tôi sẽ sử dụng nó, tôi thà làm như vậy một cách rõ ràng, thay vì giấu nó dưới một trang trí.
PM 2Ring
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.