ในความคิดเห็นเกี่ยวกับคำตอบของคำถามนี้มีคนพูดว่าพวกเขาไม่แน่ใจว่าfunctools.wraps
กำลังทำอะไรอยู่ ดังนั้นฉันถามคำถามนี้เพื่อให้มีการบันทึกไว้ใน StackOverflow สำหรับการอ้างอิงในอนาคต: สิ่งที่จะfunctools.wraps
ทำอย่างไร
ในความคิดเห็นเกี่ยวกับคำตอบของคำถามนี้มีคนพูดว่าพวกเขาไม่แน่ใจว่าfunctools.wraps
กำลังทำอะไรอยู่ ดังนั้นฉันถามคำถามนี้เพื่อให้มีการบันทึกไว้ใน StackOverflow สำหรับการอ้างอิงในอนาคต: สิ่งที่จะfunctools.wraps
ทำอย่างไร
คำตอบ:
เมื่อคุณใช้มัณฑนากรคุณจะแทนที่ฟังก์ชันหนึ่งด้วยฟังก์ชันอื่น กล่าวอีกนัยหนึ่งถ้าคุณมีมัณฑนากร
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
แล้วเมื่อคุณพูด
@logged
def f(x):
"""does some math"""
return x + x * x
มันเหมือนกับที่พูด
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
และการทำงานของคุณจะถูกแทนที่ด้วยฟังก์ชั่นf
with_logging
น่าเสียดายที่นี่หมายความว่าถ้าคุณพูดแล้ว
print(f.__name__)
มันจะพิมพ์with_logging
เพราะนั่นคือชื่อของฟังก์ชั่นใหม่ของคุณ ในความเป็นจริงถ้าคุณดูที่ docstring f
มันจะว่างเปล่าเพราะwith_logging
ไม่มี docstring ดังนั้น docstring ที่คุณเขียนจะไม่อยู่ที่นั่นอีกต่อไป นอกจากนี้ถ้าคุณมองไปที่ผล pydoc สำหรับฟังก์ชั่นที่มันจะไม่ถูกระบุว่าเป็นการหนึ่งอาร์กิวเมนต์x
; มันจะถูกระบุว่าเป็นการถ่าย*args
และ**kwargs
เพราะนั่นคือสิ่งที่ with_logging ใช้
หากใช้มัณฑนากรหมายถึงการสูญเสียข้อมูลนี้เกี่ยวกับฟังก์ชั่นมันจะเป็นปัญหาร้ายแรง functools.wraps
นั่นเป็นเหตุผลที่เรามี สิ่งนี้จะใช้ฟังก์ชั่นที่ใช้ในมัณฑนากรและเพิ่มฟังก์ชั่นในการคัดลอกชื่อฟังก์ชั่น, docstring, รายการข้อโต้แย้ง ฯลฯ และเนื่องจากwraps
ตัวมันเองเป็นมัณฑนากรรหัสต่อไปนี้ทำสิ่งที่ถูกต้อง:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
functools.wraps
งานนี้ไม่ควรเป็นเพียงส่วนหนึ่งของลวดลายตกแต่งภายในในตอนแรก คุณไม่ต้องการใช้ @wraps เมื่อใด
@wraps
เพื่อที่จะทำการดัดแปลงหรือใส่หมายเหตุประกอบต่าง ๆ ในค่าที่คัดลอกมา โดยพื้นฐานแล้วมันเป็นส่วนเสริมของปรัชญา Python ที่ชัดเจนดีกว่าโดยปริยายและกรณีพิเศษไม่ได้พิเศษพอที่จะทำลายกฎ (รหัสนั้นง่ายกว่ามากและภาษาจะเข้าใจง่ายขึ้นหาก@wraps
ต้องจัดเตรียมด้วยตนเองแทนที่จะใช้กลไกการเลือกไม่ใช้บางประเภท)
ฉันมักจะใช้ชั้นเรียนมากกว่าฟังก์ชั่นสำหรับนักตกแต่งของฉัน ฉันมีปัญหาบางอย่างกับสิ่งนี้เพราะวัตถุจะไม่มีคุณลักษณะเดียวกันทั้งหมดที่คาดว่าจะมีฟังก์ชั่น __name__
ยกตัวอย่างเช่นวัตถุจะไม่ได้มีแอตทริบิวต์ ฉันมีปัญหาเฉพาะเกี่ยวกับสิ่งนี้ซึ่งค่อนข้างยากในการติดตามที่ Django รายงานข้อผิดพลาด "วัตถุไม่มีแอตทริบิวต์ ' __name__
'" น่าเสียดายสำหรับผู้ตกแต่งในชั้นเรียนฉันไม่เชื่อว่า @wrap จะทำงานนี้ได้ ฉันได้สร้างคลาสมัณฑนากรฐานแทนเช่น:
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
ชั้นนี้ผู้รับมอบฉันทะคุณลักษณะทั้งหมดเรียกผ่านไปยังฟังก์ชั่นที่ถูกตกแต่ง ดังนั้นตอนนี้คุณสามารถสร้างมัณฑนากรง่ายๆที่ตรวจสอบว่ามีการระบุอาร์กิวเมนต์ 2 ข้อดังนี้:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
@wraps
กล่าวว่าเป็นเพียงฟังก์ชั่นอำนวยความสะดวกให้@wraps
functools.update_wrapper()
ในกรณีของมัณฑนากรชั้นคุณสามารถโทรupdate_wrapper()
โดยตรงจาก__init__()
วิธีการของคุณ ดังนั้นคุณไม่จำเป็นต้องสร้างDecBase
ที่ทุกท่านก็สามารถรวมอยู่ใน__init__()
ของเส้น:process_login
update_wrapper(self, func)
นั่นคือทั้งหมดที่
ตั้งแต่ python 3.5+:
@functools.wraps(f)
def g():
pass
g = functools.update_wrapper(g, f)
เป็นนามแฝงสำหรับ มันทำสามสิ่ง:
__module__
, __name__
, __qualname__
, __doc__
และ__annotations__
คุณลักษณะของบนf
g
รายการเริ่มต้นนี้อยู่ในWRAPPER_ASSIGNMENTS
คุณสามารถเห็นมันในแหล่ง functools__dict__
ของกับองค์ประกอบทั้งหมดจากg
f.__dict__
(ดูWRAPPER_UPDATES
ในแหล่งที่มา)__wrapped__=f
คุณลักษณะใหม่ในg
ผลที่ตามมาคือการที่g
ปรากฏว่ามีชื่อเดียวกัน docstring f
ชื่อโมดูลและลายเซ็นกว่า ปัญหาเดียวคือว่าเกี่ยวกับลายเซ็นนี้ไม่เป็นความจริง: มันเป็นเพียงที่inspect.signature
ตามโซ่เสื้อคลุมโดยค่าเริ่มต้น คุณสามารถตรวจสอบได้โดยใช้inspect.signature(g, follow_wrapped=False)
ตามที่อธิบายไว้ในเอกสาร สิ่งนี้มีผลที่น่ารำคาญ:
Signature.bind()
และดังนั้นจึงใช้สิ่งที่ต้องการขณะนี้มีความสับสนเล็กน้อยfunctools.wraps
และการตกแต่งเนื่องจากกรณีที่ใช้บ่อยมากสำหรับการพัฒนานักตกแต่งคือการห่อฟังก์ชั่น แต่ทั้งคู่เป็นแนวคิดที่เป็นอิสระอย่างสมบูรณ์ หากคุณสนใจที่จะเข้าใจความแตกต่างฉันได้ติดตั้งไลบรารีผู้ช่วยเหลือสำหรับทั้งสอง: decopatchเพื่อเขียนตัวตกแต่งได้อย่างง่ายดายและสร้างเพื่อให้ทดแทนลายเซ็นที่เก็บรักษา@wraps
ไว้ โปรดทราบว่าmakefun
อาศัยเคล็ดลับที่พิสูจน์แล้วเช่นเดียวกันกับdecorator
ห้องสมุดที่มีชื่อเสียง
นี่คือซอร์สโค้ดเกี่ยวกับ wraps:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
วิชาบังคับก่อน: คุณต้องรู้จักวิธีใช้มัณฑนากรและมีการห่อ ความคิดเห็นนี้จะอธิบายได้อย่างชัดเจนหรือลิงก์นี้ยังอธิบายได้ค่อนข้างดี
เมื่อใดก็ตามที่เราใช้ For เช่น: @wraps ตามด้วยฟังก์ชั่น wrapper ของเรา ตามรายละเอียดที่ให้ไว้ในลิงค์นี้มันบอกว่า
functools.wraps เป็นฟังก์ชั่นอำนวยความสะดวกสำหรับเรียกใช้ update_wrapper () เป็นมัณฑนากรฟังก์ชันเมื่อกำหนดฟังก์ชั่น wrapper
มันเทียบเท่ากับบางส่วน (update_wrapper, wra = wray, มอบหมาย = มอบหมาย, ปรับปรุง = ปรับปรุง)
ดังนั้น @wraps มัณฑนากรจึงให้เรียกไปยัง functools.partial (func [, * args] [, ** คำหลัก])
นิยาม functools.partial () บอกว่า
บางส่วน () ใช้สำหรับแอปพลิเคชั่นฟังก์ชั่นบางส่วนซึ่ง“ หยุด” บางส่วนของอาร์กิวเมนต์และ / หรือคำหลักของฟังก์ชันทำให้เกิดวัตถุใหม่ที่มีลายเซ็นที่เรียบง่าย ตัวอย่างเช่นบางส่วน () สามารถใช้ในการสร้าง callable ที่ทำหน้าที่เหมือนฟังก์ชั่น int () ที่อาร์กิวเมนต์ฐานเริ่มต้นที่สอง:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
ซึ่งนำฉันไปสู่ข้อสรุปว่า @wraps ให้การเรียกบางส่วน () และผ่านฟังก์ชัน wrapper ของคุณเป็นพารามิเตอร์ บางส่วน () ในที่สุดส่งกลับรุ่นที่ง่ายเช่นวัตถุของสิ่งที่อยู่ในฟังก์ชั่นการห่อหุ้มและไม่ฟังก์ชั่นการห่อหุ้มตัวเอง
ในระยะสั้นfunctools.wrapsเป็นเพียงฟังก์ชั่นปกติ ลองพิจารณาตัวอย่างที่เป็นทางการนี้ ด้วยความช่วยเหลือของซอร์สโค้ดเราสามารถดูรายละเอียดเพิ่มเติมเกี่ยวกับการใช้งานและขั้นตอนการทำงานดังนี้:
เสื้อคลุม = O1 .__ โทร __ (เสื้อคลุม)
การตรวจสอบการใช้งาน__call__เราจะเห็นว่าหลังจากขั้นตอนนี้wrapper (ด้านซ้ายมือ) กลายเป็นวัตถุที่เกิดจากself.func (* self.args, * args, newkeywords **) การตรวจสอบการสร้างO1ใน__new__เรา รู้self.funcเป็นฟังก์ชั่นupdate_wrapper มันใช้พารามิเตอร์* args , wrapperด้านขวาเป็นพารามิเตอร์ที่ 1 เมื่อตรวจสอบขั้นตอนสุดท้ายของupdate_wrapperเราจะเห็นว่ามีการส่งคืนwrapperทางด้านขวาด้วยคุณลักษณะบางอย่างที่ปรับเปลี่ยนตามต้องการ