มีฟังก์ชั่นระบุตัวตนในหลามหรือไม่


145

ฉันอยากจะชี้ไปที่ฟังก์ชั่นที่ไม่ทำอะไรเลย:

def identity(*args)
    return args

กรณีการใช้งานของฉันเป็นแบบนี้

try:
    gettext.find(...)
    ...
    _ = gettext.gettext
else:
    _ = identity

แน่นอนฉันสามารถใช้สิ่งที่identityกำหนดข้างต้นได้ แต่ตัวในตัวจะทำงานได้เร็วขึ้นอย่างแน่นอน (และหลีกเลี่ยงข้อบกพร่องที่แนะนำด้วยตัวเอง)

เห็นได้ชัดว่าmapและfilterใช้Noneสำหรับข้อมูลประจำตัว แต่นี่เป็นเฉพาะสำหรับการใช้งานของพวกเขา

>>> _=None
>>> _("hello")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

6
คุณหมายถึงmap and filter use None for the identityอะไร
Matt Fenwick

15
@MattFenwick:map(None, [1, 2, 3])
Greg Hewgill

6
ตรวจสอบค่าตอบแทน ตัวแปร args ของคุณจะเป็นลำดับ (ในสถานการณ์นี้) หนึ่งค่าดังนั้นอย่าเว้นเครื่องหมายดอกจันในการประกาศหรือนำออกจากกล่องก่อนที่จะส่งคืน
เดิร์ค

11
@GregHewgill: น่าเศร้าที่ใช้ไม่ได้กับ Python 3.x
Ethan Furman

6
@ GregHewgill ฉันไม่ดี ฉันเอาสิ่งนั้นมาจากหมอหลังจาก googling แต่ Python2.x doc จะมาก่อนเสมอ ...
rds

คำตอบ:


99

ทำการวิจัยเพิ่มเติมไม่มีคุณสมบัติถูกถามในปัญหา 1673203และจากRaymond Hettinger กล่าวว่าจะไม่มี :

ดีกว่าที่จะให้ผู้คนเขียน pass-throughs เล็กน้อยของพวกเขาและคิดเกี่ยวกับค่าใช้จ่ายของลายเซ็นและเวลา

ดังนั้นวิธีที่ดีกว่าที่จะทำคือจริง ๆ (แลมบ์ดาหลีกเลี่ยงการตั้งชื่อฟังก์ชั่น):

_ = lambda *args: args
  • ข้อได้เปรียบ: ใช้พารามิเตอร์จำนวนเท่าใดก็ได้
  • ข้อเสีย: ผลลัพธ์เป็นพารามิเตอร์ชนิดบรรจุกล่อง

หรือ

_ = lambda x: x
  • ข้อได้เปรียบ: ไม่เปลี่ยนประเภทของพารามิเตอร์
  • ข้อเสีย: ใช้พารามิเตอร์ตำแหน่ง 1 พารามิเตอร์

13
โปรดทราบว่านี่ไม่ใช่ฟังก์ชันตัวตน
Marcin

1
@Marcin ขอบคุณสำหรับข้อสังเกต ฉันได้เพิ่มข้อดี / ข้อเสียของทั้งสองเพื่อไม่ให้ใครเข้าใจผิด และตอนนี้ผมเชื่อว่ามีควรจะได้รับฟังก์ชั่นในตัวที่รับจำนวนพารามิเตอร์ใด ๆ และเป็นตัวตนที่แท้จริง :)
RDS

7
คำตอบที่ดี อย่างไรก็ตามฟังก์ชั่นการระบุตัวตนที่แท้จริงจะส่งคืนอะไรเมื่อรับพารามิเตอร์หลาย ๆ ตัว
Marcin

5
@Marcin: ไม่เพียงแค่ไปตามสิ่งที่เขาถามในคำถามของเขา
Ethan Furman

4
ใช่ขอบคุณฉันมีlambda x: xฟังก์ชั่นตัวตนเล็ก ๆ น้อย ๆที่เหมาะกับพารามิเตอร์สตริงหนึ่งตัว @Marcin ฉันหวังว่าฉันสามารถทำได้lambda *args: *args:-)
rds

28

ฟังก์ชันตัวตนตามที่กำหนดไว้ในhttps://en.wikipedia.org/wiki/Identity_functionใช้อาร์กิวเมนต์ตัวเดียวและคืนค่ามันไม่เปลี่ยนแปลง:

def identity(x):
    return x

สิ่งที่คุณจะถามหาเมื่อคุณบอกว่าคุณต้องการลายเซ็นdef identity(*args)คือไม่เคร่งครัดฟังก์ชั่นตัวตนตามที่คุณต้องการที่จะใช้ข้อโต้แย้งหลาย ไม่เป็นไร แต่เมื่อคุณประสบปัญหาเนื่องจากฟังก์ชั่น Python ไม่ส่งคืนผลลัพธ์หลายรายการดังนั้นคุณต้องหาวิธียัดเยียดข้อโต้แย้งเหล่านั้นทั้งหมดให้เป็นค่าส่งคืนเดียว

วิธีปกติในการส่งคืน "ค่าหลายค่า" ใน Python ก็คือคืนค่า tuple ของค่า - ในทางเทคนิคแล้วมันคือค่าส่งคืนหนึ่งค่า แต่สามารถใช้ได้ในบริบทส่วนใหญ่ราวกับว่ามันเป็นค่าหลายค่า แต่การทำเช่นนี้หมายความว่าคุณได้รับ

>>> def mv_identity(*args):
...     return args
...
>>> mv_identity(1,2,3)
(1, 2, 3)
>>> # So far, so good. But what happens now with single arguments?
>>> mv_identity(1)
(1,)

และแก้ไขปัญหานั้นได้อย่างรวดเร็วให้ปัญหาอื่น ๆ ตามที่ได้แสดงคำตอบต่าง ๆ ที่นี่

โดยสรุปแล้วไม่มีฟังก์ชั่นระบุตัวตนใน Python เพราะ:

  1. การนิยามอย่างเป็นทางการ (ฟังก์ชั่นการโต้แย้งเดียว) นั้นมีประโยชน์และไม่สำคัญที่จะเขียน
  2. การขยายคำจำกัดความไปสู่ข้อโต้แย้งหลาย ๆ อย่างนั้นไม่ได้กำหนดไว้อย่างชัดเจนและคุณดีกว่าที่จะกำหนดรุ่นของคุณเองที่ทำงานในแบบที่คุณต้องการสำหรับสถานการณ์เฉพาะของคุณ

สำหรับกรณีที่แม่นยำของคุณ

def dummy_gettext(message):
    return message

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


ฉันไม่เห็นคำตอบที่คุณอ้างถึงด้วย "การแก้ไขปัญหานั้นจะทำให้เกิดปัญหาอื่น ๆ ตามที่ได้แสดงคำตอบแล้ว" id= lambda *args: args if len(args)>1 else args[0]โดยเฉพาะมันพอที่จะใช้งาน
สูงสุด

21

คุณจะทำงานได้ดี เมื่อจำนวนพารามิเตอร์ถูกแก้ไขคุณสามารถใช้ฟังก์ชันที่ไม่ระบุชื่อดังนี้:

lambda x: x

8
คุณสามารถทำเช่นนี้กับ varargs lambda *args: argsเกินไป: มันเป็นตัวเลือกโวหารจริง ๆ

ฉันชอบอันดับที่สองมากขึ้นเพราะต้องใช้การโต้แย้งจำนวนมาก
rds

4
@delnan @rds - *argsรุ่นนี้มีประเภทผลตอบแทนแตกต่างกันดังนั้นจึงไม่เทียบเท่าแม้แต่กรณีอาร์กิวเมนต์เดี่ยว
Marcin

8
@delnan: คุณบอกว่ามันเป็นตัวเลือกโวหารซึ่งไม่ถูกต้องซึ่งหมายความว่าไม่มีความแตกต่างในความหมายของทั้งสองรูปแบบ
Marcin

1
@Marcin: มันโชคร้ายถ้าฉันบอกเป็นนัย ๆ ฉันหมายถึงตัวเลือกระหว่างdefและlambdaสำหรับฟังก์ชั่นที่เรียบง่ายเช่นนั้น

7

ไม่มีฟังก์ชั่นระบุตัวตนใน Python การเลียนแบบฟังก์ชันของ Haskell คือid :

identity = lambda x, *args: (x,) + args if args else x

ตัวอย่างการใช้งาน:

identity(1)
1
identity(1,2)
(1, 2)

เนื่องจากidentityไม่ทำอะไรเลยยกเว้นการส่งคืนอาร์กิวเมนต์ที่กำหนดฉันไม่คิดว่ามันจะช้ากว่าการปรับใช้แบบดั้งเดิม


มันคือการสร้างการโทรที่ต้องใช้เวลาไม่ว่าคุณจะทำอะไรหลังจากการตั้งค่านั้นเสร็จสมบูรณ์
chepner

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

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

6

ไม่ไม่มี

โปรดทราบว่าidentity:

  1. เทียบเท่ากับ lambda * args: args
  2. จะกล่อง args ของมัน - เช่น

    In [6]: id = lambda *args: args
    
    In [7]: id(3)
    Out[7]: (3,)

ดังนั้นคุณอาจต้องการใช้lambda arg: argถ้าคุณต้องการฟังก์ชั่นตัวตนที่แท้จริง

หมายเหตุ: ตัวอย่างนี้จะแรเงาidฟังก์ชั่นในตัว(ซึ่งคุณอาจไม่เคยใช้)


1
โปรดทราบว่า id เป็นฟังก์ชันในตัวและตัวอย่างนี้จะเขียนทับมัน
Arnie97

@ Arnie97 ยุติธรรม! ฉันลืมid
Marcin

4

หากความเร็วไม่สำคัญนี่ควรจัดการทุกกรณี:

def identity(*args, **kwargs):
    if not args:
        if not kwargs:
            return None
        elif len(kwargs) == 1:
            return  next(iter(kwargs.values()))
        else:
            return (*kwargs.values(),)
    elif not kwargs:
        if len(args) == 1:
            return args[0]
        else:
            return args
    else:
        return (*args, *kwargs.values())

ตัวอย่างการใช้งาน:

print(identity())
None
$identity(1)
1
$ identity(1, 2)
(1, 2)
$ identity(1, b=2)
(1, 2)
$ identity(a=1, b=2)
(1, 2)
$ identity(1, 2, c=3)
(1, 2, 3)

1

Stub ของฟังก์ชั่นการโต้แย้งเดียว

gettext.gettext(กรณีใช้ตัวอย่างของ OP) messageยอมรับอาร์กิวเมนต์เดียว หากต้องการต้นขั้วมันไม่มีเหตุผลที่จะกลับมา[message]แทนmessage( def identity(*args): return args) ดังนั้นทั้งคู่

_ = lambda message: message

def _(message):
    return message

ลงตัว

... แต่ในตัวจะทำงานได้เร็วขึ้นอย่างแน่นอน (และหลีกเลี่ยงข้อบกพร่องที่แนะนำโดยตัวเอง)

ข้อบกพร่องในกรณีเล็กน้อยนั้นแทบไม่มีความเกี่ยวข้อง สำหรับอาร์กิวเมนต์ประเภทที่กำหนดไว้ล่วงหน้าให้พูดว่าstrเราสามารถใช้str()ตัวเองเป็นฟังก์ชั่นระบุตัวตน (เพราะสตริงฝึกงานมันยังคงเอกลักษณ์ของวัตถุดูidหมายเหตุด้านล่าง) และเปรียบเทียบประสิทธิภาพกับโซลูชั่นแลมบ์ดา:

$ python3 -m timeit -s "f = lambda m: m" "f('foo')"
10000000 loops, best of 3: 0.0852 usec per loop
$ python3 -m timeit "str('foo')"
10000000 loops, best of 3: 0.107 usec per loop

การเพิ่มประสิทธิภาพขนาดเล็กเป็นไปได้ ตัวอย่างเช่นรหัสCythonต่อไปนี้:

test.pyx

cpdef str f(str message):
    return message

แล้ว:

$ pip install runcython3
$ makecython3 test.pyx
$ python3 -m timeit -s "from test import f" "f('foo')"
10000000 loops, best of 3: 0.0317 usec per loop

ฟังก์ชันตัวระบุวัตถุในตัว

อย่าสับสนฟังก์ชั่นเอกลักษณ์ด้วยฟังก์ชั่นในidตัวซึ่งจะส่งกลับ'เอกลักษณ์' ของวัตถุ (หมายถึงตัวระบุเฉพาะสำหรับวัตถุนั้นมากกว่าค่าของวัตถุนั้นเมื่อเทียบกับ==ผู้ปฏิบัติงาน) ที่อยู่หน่วยความจำใน CPython


การเร่งความเร็ว 40% "ดูเหมือนจะไม่คุ้มกับมันมากนัก"? ในกรณีที่ข้อมูลระบุตัวตนทำงานเป็น "ตัวกรองเริ่มต้น" สำหรับฟังก์ชั่นที่ทำงานให้พูดหนึ่งครั้งต่อช่องบนภาพ 10,000x10,000 พิกเซล (อาจไม่ใช่ทุกวัน แต่ไม่แน่นอนทุกวัน) นั่นคือความแตกต่างระหว่าง 25 และ 9 วินาทีของเวลาดำเนินการ! ขอบคุณสำหรับตัวอย่างของ Cython
9999 ปี

@ 9999 ปีฉันเห็นด้วย ฉันลบความคิดเห็นที่คุ้มค่าแล้ว ขอบคุณสำหรับการปรับปรุงคำตอบด้วย ฉันได้ทำการเปลี่ยนแปลงเล็กน้อยบนของคุณ
saaj

หากคุณมีภาพขนาด 10,000x10,000 พิกเซลฉันขอแนะนำอย่างยิ่งให้ใช้การดำเนินการแบบเวกเตอร์โดยใช้บางสิ่งบางอย่างอย่าง numpy มันจะเร็วกว่านี้ใช้ความจำน้อยลงและไม่จำเป็นต้องเขียนโค้ด cython
anthonybell

-2

ด้ายค่อนข้างเก่า แต่ยังต้องการโพสต์นี้

เป็นไปได้ที่จะสร้างวิธีการระบุตัวตนสำหรับทั้งข้อโต้แย้งและวัตถุ ในตัวอย่างด้านล่าง ObjOut เป็นข้อมูลเฉพาะตัวของ ObjIn ตัวอย่างอื่น ๆ ทั้งหมดข้างต้นไม่ได้จัดการกับ dict ** kwargs

class test(object):
    def __init__(self,*args,**kwargs):
        self.args = args
        self.kwargs = kwargs
    def identity (self):
        return self

objIn=test('arg-1','arg-2','arg-3','arg-n',key1=1,key2=2,key3=3,keyn='n')
objOut=objIn.identity()
print('args=',objOut.args,'kwargs=',objOut.kwargs)

#If you want just the arguments to be printed...
print(test('arg-1','arg-2','arg-3','arg-n',key1=1,key2=2,key3=3,keyn='n').identity().args)
print(test('arg-1','arg-2','arg-3','arg-n',key1=1,key2=2,key3=3,keyn='n').identity().kwargs)

$ py test.py
args= ('arg-1', 'arg-2', 'arg-3', 'arg-n') kwargs= {'key1': 1, 'keyn': 'n', 'key2': 2, 'key3': 3}
('arg-1', 'arg-2', 'arg-3', 'arg-n')
{'key1': 1, 'keyn': 'n', 'key2': 2, 'key3': 3}

นี่ดูเหมือนการอ้างอิงถ้าใช่แล้วมันมาจากที่ไหน?
Jeff Puckett

@JeffPuckettII ฉันไม่ได้ติดตามคำถามของคุณ คุณกำลังถามว่าวัตถุใหม่เป็นการอ้างอิงหรือไม่?
Sud

คุณใช้ไฮไลต์บล็อกโคตสำหรับ "เป็นไปได้ที่จะสร้างข้อมูลประจำตัว ... " ซึ่งหมายถึงการอ้างอิงจากแหล่งอื่น หากสิ่งเหล่านี้เป็นคำพูดของคุณเองฉันขอแนะนำไม่เน้นเป็นคำพูด ไม่ใช่เรื่องใหญ่จริงๆ แต่ถ้านี่คือคำพูดจากแหล่งอื่นคุณควรรวมการอ้างอิงถึงมัน
Jeff Puckett

คุณจะทำอย่างไรตอบคำถามเดิมmap(identity, [1, 2, 3])ผลตอบแทน[1, 2, 3]?
rds

class test1(object): def __init__(self,*args,**kwargs): self.args = args self.kwargs = kwargs def identity (self): return self.args print(test1([1,2,3]).identity())-> ผลลัพธ์: ([1, 2, 3],)
Sud
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.