อะไรคือการใช้งานทั่วไปสำหรับผู้ตกแต่ง Python [ปิด]


337

ในขณะที่ฉันชอบคิดว่าตัวเองเป็น coder Python ที่มีความสามารถพอสมควร แต่อีกด้านหนึ่งของภาษาที่ฉันไม่เคยคิดเลยก็คือนักตกแต่ง

ฉันรู้ว่าพวกเขาคืออะไร (เผินๆ) ฉันได้อ่านบทช่วยสอนตัวอย่างคำถามเกี่ยวกับ Stack Overflow และฉันเข้าใจไวยากรณ์สามารถเขียนของตัวเองได้บางครั้งใช้ @classmethod และ @staticmethod แต่ฉันไม่เคยใช้ มัณฑนากรเพื่อแก้ปัญหาในรหัส Python ของฉันเอง ฉันไม่เคยเจอปัญหาที่ฉันคิดว่า "อืม ... นี่มันดูเหมือนงานสำหรับมัณฑนากร!"

ดังนั้นฉันจึงสงสัยว่าพวกคุณจะเสนอตัวอย่างที่คุณใช้ตกแต่งภายในในโปรแกรมของคุณเองหรือไม่และหวังว่าฉันจะมี "A-ha!" ช่วงเวลาและรับพวกเขา


5
นอกจากนี้ตัวตกแต่งยังมีประโยชน์สำหรับการบันทึก - นั่นคือการแคชผลลัพธ์ช้าไปสู่การคำนวณของฟังก์ชัน มัณฑนากรสามารถกลับฟังก์ชั่นที่ตรวจสอบอินพุตและหากพวกเขาได้รับการนำเสนอแล้วให้ส่งคืนผลการแคช
ปีเตอร์

1
โปรดทราบว่า Python มีมัณฑนากรในตัวfunctools.lru_cacheซึ่งทำสิ่งที่ Peter พูดตั้งแต่ Python 3.2 ออกมาในเดือนกุมภาพันธ์ 2011
Taegyung

เนื้อหาของPython Decorator Libraryควรให้ความคิดที่ดีเกี่ยวกับการใช้งานอื่น ๆ
martineau

คำตอบ:


126

ฉันใช้มัณฑนากรเป็นหลักในการกำหนดเวลา

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
ภายใต้ Unix time.clock()วัดเวลา CPU คุณอาจต้องการใช้time.time()แทนหากคุณต้องการวัดเวลานาฬิกาแขวน
Jabba

20
เยี่ยมมาก! ไม่รู้เลยว่ามันทำอะไร คำอธิบายว่าคุณกำลังทำอะไรที่นี่และวิธีการที่มัณฑนากรแก้ปัญหาจะดีมาก
MeLight

7
มันวัดเวลาที่ใช้ในmyFunctionการวิ่ง ...
RSabet

98

ฉันใช้มันเพื่อการซิงโครไนซ์

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

ตามที่ระบุไว้ในความคิดเห็นตั้งแต่ Python 2.5 คุณสามารถใช้withคำสั่งร่วมกับวัตถุ threading.Lock(หรือmultiprocessing.Lockตั้งแต่รุ่น 2.6) เพื่อทำให้การใช้งานของ decorator ง่ายขึ้นเพียง:

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

ไม่ว่าคุณจะใช้มันในลักษณะนี้:

import threading
lock = threading.Lock()

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

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

โดยพื้นฐานแล้วมันแค่วางlock.acquire()/ lock.release()ที่ทั้งสองด้านของการเรียกใช้ฟังก์ชัน


18
อาจเป็นธรรม แต่นักตกแต่งจะสับสนโดยเนื้อแท้ เพื่อ noobs ปีแรกที่อยู่ข้างหลังคุณและลองแก้ไขรหัสของคุณ หลีกเลี่ยงสิ่งนี้ด้วยความเรียบง่าย: ให้ do_something () ล้อมโค้ดไว้ในบล็อกใต้ 'with lock:' และทุกคนสามารถเห็นจุดประสงค์ของคุณได้อย่างชัดเจน นักตกแต่งมีการใช้งานมากเกินไปอย่างล้นหลามโดยผู้ที่ต้องการดูสมาร์ท (และหลายคนเป็นจริง) แต่แล้วรหัสมาเพียงมนุษย์ปุถุชนและได้รับการขึ้น - ลง
Kevin J. Rice

18
@ KevinJ.Rice จำกัด รหัสของคุณเพื่อให้ 'noobs ปีแรก' เข้าใจได้ดีขึ้นว่าเป็นการฝึกที่แย่มาก ไวยากรณ์มัณฑนากรง่ายต่อการอ่านและถอดรหัสรหัสได้อย่างมาก
TaylerJones

18
@TaylerJones การอ่านโค้ดเป็นเรื่องสำคัญที่สุดของฉันเมื่อเขียน โค้ดจะอ่าน 7+ ครั้งทุกครั้งที่มีการแก้ไข รหัสที่เข้าใจยาก (สำหรับ noobs หรือสำหรับผู้เชี่ยวชาญที่ทำงานภายใต้ความกดดันเรื่องเวลา) เป็นหนี้ทางเทคนิคที่ต้องชำระทุกครั้งที่มีคนเยี่ยมชมต้นไม้ต้นกำเนิด
Kevin J. Rice

@TaylerJones หนึ่งในภารกิจที่สำคัญที่สุดสำหรับโปรแกรมเมอร์คือการส่งมอบความชัดเจน
JDOaktown

71

ฉันใช้มัณฑนากรเพื่อตรวจสอบค่าพารามิเตอร์ซึ่งส่งผ่านไปยังวิธี Python ผ่าน RMI ดังนั้นแทนที่จะทำซ้ำการนับพารามิเตอร์เดียวกันซ้ำซาก mumbo-jumbo อีกครั้งและอีกครั้ง

ตัวอย่างเช่นแทนที่จะเป็น:

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

ฉันเพิ่งประกาศ:

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

และaccepts()ทำงานทั้งหมดให้ฉัน


15
สำหรับทุกคนที่สนใจมีการดำเนินการ@acceptsใน PEP 318
มาร์ติโน

2
ผมคิดว่ามีการพิมพ์ผิด .. วิธีแรกควรจะยอมรับ .. คุณประกาศทั้งในฐานะ "MyMethod"
DevC

1
@DevC ไม่มันไม่เหมือนพิมพ์ผิด เนื่องจากเห็นได้ชัดว่าไม่ใช่การดำเนินการของ "accepts (.. )" และที่นี่ "accepts (.. )" ไม่ทำงานที่จะทำโดยสองบรรทัดในช่วงเริ่มต้นของ "myMethod (.. )" - นั่นคือ การตีความเท่านั้นที่เหมาะ
Evgeni Sergeev

1
ขออภัยสำหรับการชนฉันแค่อยากจะชี้ให้เห็นว่าการตรวจสอบประเภทของข้อโต้แย้งที่ผ่านไปและยกระดับ TypeError ไม่เช่นนั้นจะถือว่าเป็นแนวปฏิบัติที่ไม่ดีเพราะมันจะไม่ยอมรับเช่น int ถ้าตรวจสอบเฉพาะการลอยตัวและเพราะโดยปกติ รหัสควรปรับให้เข้ากับค่าต่าง ๆ ที่ส่งผ่านเพื่อความยืดหยุ่นสูงสุด
Gustavo6046

2
วิธีที่แนะนำในการตรวจสอบชนิดใน Python คือผ่านisinstance()ฟังก์ชั่นในตัวเนื่องจากทำในการนำ PEP 318 ไปใช้กับมัณฑนากร เนื่องจากclassinfoอาร์กิวเมนต์สามารถเป็นหนึ่งประเภทขึ้นไปการใช้มันจะช่วยลดการคัดค้านของ @ Gustavo6046 (ใช้ได้) Python มีNumberคลาสพื้นฐานที่เป็นนามธรรมดังนั้นการทดสอบทั่วไปisinstance(42, numbers.Number)จึงเป็นไปได้
martineau

48

มัณฑนากรใช้สำหรับทุกสิ่งที่คุณต้องการ "ตัด" อย่างโปร่งใสด้วยฟังก์ชันเพิ่มเติม

Django ใช้เพื่อห่อฟังก์ชั่น "จำเป็นต้องเข้าสู่ระบบ" ในฟังก์ชั่นการดูเช่นเดียวกับการลงทะเบียนฟังก์ชั่นตัวกรองลงทะเบียนฟังก์ชั่นกรอง

คุณสามารถใช้ตัวตกแต่งคลาสเพื่อเพิ่มบันทึกที่มีชื่อในคลาสการเพิ่มบันทึกชื่อในชั้นเรียน

ฟังก์ชั่นทั่วไปที่เพียงพอที่คุณสามารถ "ยึดติด" กับคลาสที่มีอยู่หรือพฤติกรรมของฟังก์ชั่นเป็นเกมที่ยุติธรรมสำหรับการตกแต่ง

นอกจากนี้ยังมีการอภิปรายของกรณีการใช้งานในกลุ่มข่าวสารหลาม-Devชี้ไปตามPEP 318 - ตกแต่งสำหรับฟังก์ชั่นและวิธีการ


Cherrypy ใช้ @ cherrypy.expose เพื่อให้ฟังก์ชั่นตรงเป็นสาธารณะและฟังก์ชั่นที่ซ่อนอยู่ นั่นคือการแนะนำครั้งแรกของฉันและฉันได้คุ้นเคยกับมันที่นั่น
Marc Maxmeister

26

สำหรับ nosetests คุณสามารถเขียนมัณฑนากรที่จัดหาฟังก์ชั่นการทดสอบหน่วยหรือวิธีการที่มีพารามิเตอร์หลายชุด:

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

23

ห้องสมุด Twisted ใช้มัณฑนากรรวมกับเครื่องกำเนิดไฟฟ้าเพื่อให้ภาพลวงตาว่าฟังก์ชั่นอะซิงโครนัสเป็นแบบซิงโครนัส ตัวอย่างเช่น:

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

เมื่อใช้สิ่งนี้โค้ดที่จะถูกแบ่งออกเป็นฟังก์ชั่นการโทรกลับจำนวนเล็กน้อยสามารถเขียนได้ค่อนข้างเป็นธรรมชาติในบล็อกเดียวทำให้ง่ายต่อการเข้าใจและบำรุงรักษา


14

การใช้งานที่ชัดเจนอย่างหนึ่งสำหรับการบันทึกคือ:

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

ฉันใช้พวกเขาเป็นหลักสำหรับการแก้ไขข้อบกพร่อง (wrapper รอบฟังก์ชั่นที่พิมพ์ข้อโต้แย้งและผลของมัน) และการตรวจสอบ (เช่นเพื่อตรวจสอบว่าอาร์กิวเมนต์เป็นประเภทที่ถูกต้องหรือในกรณีของเว็บแอปพลิเคชันถ้าผู้ใช้มีสิทธิ์เพียงพอ วิธี).


6

ฉันใช้มัณฑนากรต่อไปนี้เพื่อสร้างฟังก์ชัน threadsafe ทำให้โค้ดอ่านง่ายขึ้น มันเกือบจะคล้ายกับที่เสนอโดย John Fouhy แต่ความแตกต่างคือการทำงานในฟังก์ชั่นเดียวและไม่จำเป็นต้องสร้างวัตถุล็อคอย่างชัดเจน

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
สิ่งนี้หมายความว่าแต่ละฟังก์ชั่นที่ตกแต่งแล้วมีล็อคของตัวเองหรือไม่?
เสียใจ

1
@grieve ใช่ทุกครั้งที่มีการใช้มัณฑนากร (เรียกว่า) มันจะสร้างวัตถุล็อคใหม่สำหรับฟังก์ชั่น / วิธีการตกแต่ง
martineau

5
มันอันตรายจริงๆ เมธอด inc_var () คือ "threadsafe" โดยมีเพียงหนึ่งคนเท่านั้นที่สามารถโทรหาได้ในแต่ละครั้ง ที่กล่าวว่าเนื่องจากวิธีการทำงานกับตัวแปรสมาชิก "var" และวิธีการอื่น ๆ น่าจะทำงานกับตัวแปรสมาชิก "var" และการเข้าถึงเหล่านั้นไม่ได้เป็น threadsafe เนื่องจากการล็อคไม่ได้ถูกแชร์ การทำสิ่งต่าง ๆ ด้วยวิธีนี้ทำให้ผู้ใช้ระดับ X มีความรู้สึกผิดพลาดด้านความปลอดภัย
Bob Van Zant

ไม่ปลอดภัยจนกว่าจะใช้การล็อคครั้งเดียว
Chandu

5

มัณฑนากรถูกนำมาใช้เพื่อกำหนดคุณสมบัติของฟังก์ชั่นหรือเป็นสำเร็จรูปที่เปลี่ยนแปลงมัน; เป็นไปได้ แต่ตอบโต้ง่ายสำหรับพวกเขาที่จะกลับมาทำงานที่แตกต่างอย่างสิ้นเชิง ดูคำตอบอื่น ๆ ที่นี่ดูเหมือนว่าหนึ่งในการใช้งานที่พบบ่อยที่สุดคือการ จำกัด ขอบเขตของกระบวนการอื่น ๆ ไม่ว่าจะเป็นการบันทึกการทำโปรไฟล์การตรวจสอบความปลอดภัย ฯลฯ

CherryPy ใช้การส่งวัตถุเพื่อจับคู่ URL กับวัตถุและในที่สุดวิธีการ ผู้ตกแต่งในวิธีการเหล่านั้นส่งสัญญาณว่า CherryPy ได้รับอนุญาตให้ใช้วิธีการเหล่านั้นได้หรือไม่ ตัวอย่างเช่นดัดแปลงมาจากบทช่วยสอน :

class HelloWorld:

    ...

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

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

cherrypy.quickstart(HelloWorld())

นี่ไม่เป็นความจริง. มัณฑนากรสามารถเปลี่ยนพฤติกรรมของฟังก์ชันได้อย่างสมบูรณ์
39904 recursive

ตกลง. แต่มัณฑนากร "เปลี่ยนพฤติกรรมของฟังก์ชั่นได้บ่อยเพียงใด" จากสิ่งที่ฉันเห็นเมื่อพวกเขาไม่ได้ใช้เพื่อระบุคุณสมบัติพวกเขาเพียงแค่ใช้สำหรับรหัสสำเร็จรูป ฉันแก้ไขคำตอบแล้ว
Nikhil Chelliah

5

ฉันใช้มันเมื่อเร็ว ๆ นี้ในขณะที่ทำงานกับเว็บแอปพลิเคชันเครือข่ายสังคม สำหรับชุมชน / กลุ่มฉันควรให้สิทธิ์การเป็นสมาชิกเพื่อสร้างการสนทนาใหม่และตอบกลับข้อความที่คุณต้องเป็นสมาชิกของกลุ่มนั้น ดังนั้นฉันจึงเขียนมัณฑนากร@membership_requiredและวางสิ่งที่ฉันต้องการในมุมมองของฉัน


1

ฉันใช้มัณฑนากรนี้เพื่อแก้ไขพารามิเตอร์

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']

สิ่งนี้เขียนเมื่อฉัน refactor ฟังก์ชั่นบางอย่างจำเป็นต้องผ่านการโต้แย้ง "wanN" แต่ในรหัสเก่าของฉันฉันผ่าน N หรือ 'N' เท่านั้น


1

มัณฑนากรสามารถใช้สร้างตัวแปรวิธีการทำงานได้อย่างง่ายดาย

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
ขอบคุณสำหรับตัวอย่างของคุณ แต่ (apolgies) ฉันต้องพูด WTF - ทำไมคุณถึงใช้มัน มันมีศักยภาพมากมายสำหรับการทำให้ผู้คนสับสน แน่นอนฉันเคารพความต้องการการใช้เคส - ขอบ แต่คุณกำลังเจอกับปัญหาที่พบบ่อยๆที่ Python devs ที่ไม่มีประสบการณ์มี - ไม่ได้ใช้คลาสที่เพียงพอ นั่นคือเพียงแค่มีการนับคลาสอย่างง่าย ๆ เตรียมใช้งานและใช้งาน Noobs มีแนวโน้มที่จะเขียนแบบหล่นผ่าน (รหัสที่ไม่ใช่ระดับชั้นเรียน) และพยายามที่จะรับมือกับการขาดฟังก์ชั่นชั้นเรียนที่มีการแก้ปัญหาที่ซับซ้อน ได้โปรดอย่า กรุณา? ขออภัยที่พิณขอบคุณสำหรับคำตอบของคุณ แต่คุณกดปุ่มร้อนสำหรับฉัน
เควินเจไรซ์

ฉันจะเป็น -1 ในเรื่องนี้ถ้ามันแสดงให้เห็นว่าเป็นคำขอดึงให้ฉันตรวจสอบรหัสและดังนั้นฉันยัง -1 ในเรื่องนี้เป็นงูหลามที่ดี
Techdragon

น่ารัก โง่ แต่น่ารัก :) ฉันไม่ได้สนใจคุณลักษณะของฟังก์ชั่นเป็นครั้งคราว แต่มันเป็นของหายากในรหัส Python ทั่วไปที่ถ้าฉันจะใช้หนึ่งฉันจะทำอย่างชัดเจนแทนที่จะซ่อนไว้ใต้มัณฑนากร
PM 2Ring
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.