บังคับตั้งชื่อพารามิเตอร์ใน Python


111

ใน Python คุณอาจมีนิยามฟังก์ชัน:

def info(object, spacing=10, collapse=1)

ซึ่งสามารถเรียกได้ด้วยวิธีใด ๆ ต่อไปนี้:

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

ต้องขอบคุณ Python ที่อนุญาตให้มีอาร์กิวเมนต์ลำดับใดก็ได้ตราบเท่าที่มีการตั้งชื่อ

ปัญหาที่เราพบคือเมื่อฟังก์ชันที่ใหญ่ขึ้นบางส่วนของเราเติบโตขึ้นผู้คนอาจเพิ่มพารามิเตอร์ระหว่างspacingและcollapseซึ่งหมายความว่าค่าที่ไม่ถูกต้องอาจไปสู่พารามิเตอร์ที่ไม่มีชื่อ นอกจากนี้บางครั้งมันก็ไม่ชัดเจนเสมอไปว่าต้องทำอะไรบ้างเรากำลังหาวิธีบังคับให้ผู้คนตั้งชื่อพารามิเตอร์บางอย่างไม่ใช่แค่มาตรฐานการเข้ารหัส แต่ควรเป็นแฟล็กหรือปลั๊กอิน pydev?

ดังนั้นใน 4 ตัวอย่างข้างต้นสุดท้ายเท่านั้นที่จะผ่านการตรวจสอบเนื่องจากพารามิเตอร์ทั้งหมดถูกตั้งชื่อ

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

คำตอบ:


214

ใน Python 3 - ใช่คุณสามารถระบุ*ในรายการอาร์กิวเมนต์

จากเอกสาร :

พารามิเตอร์หลัง“ *” หรือ“ * ตัวระบุ” เป็นพารามิเตอร์คำหลักเท่านั้นและสามารถส่งผ่านได้เฉพาะอาร์กิวเมนต์คำหลักที่ใช้เท่านั้น

>>> def foo(pos, *, forcenamed):
...   print(pos, forcenamed)
... 
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)

นอกจากนี้ยังสามารถใช้ร่วมกับ**kwargs:

def foo(pos, *, forcenamed, **kwargs):

32

คุณสามารถบังคับให้บุคคลอื่นใช้อาร์กิวเมนต์คำสำคัญใน Python3 ได้โดยกำหนดฟังก์ชันด้วยวิธีต่อไปนี้

def foo(*, arg0="default0", arg1="default1", arg2="default2"):
    pass

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

def foo(**kwargs):
    pass

นั่นจะบังคับให้ผู้โทรใช้ kwargs แต่นี่ไม่ใช่วิธีแก้ปัญหาที่ยอดเยี่ยมเนื่องจากคุณต้องทำเครื่องหมายเพื่อยอมรับเฉพาะอาร์กิวเมนต์ที่คุณต้องการเท่านั้น


11

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

__named_only_start = object()

def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
    if _p is not __named_only_start:
        raise TypeError("info() takes at most 3 positional arguments")
    return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)

วิธีเดียวที่ผู้โทรจะสามารถให้ข้อโต้แย้งspacingและระบุcollapseตำแหน่ง (โดยไม่มีข้อยกเว้น) คือ:

info(arg1, arg2, arg3, module.__named_only_start, 11, 2)

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

มิฉะนั้นการโทรจะต้องอยู่ในรูปแบบ:

info(arg1, arg2, arg3, spacing=11, collapse=2)

โทร

info(arg1, arg2, arg3, 11, 2)

จะกำหนดค่า 11 ให้กับพารามิเตอร์_pและข้อยกเว้นที่เพิ่มขึ้นจากคำสั่งแรกของฟังก์ชัน

ลักษณะเฉพาะ:

  • พารามิเตอร์ก่อน_p=__named_only_startได้รับการยอมรับในตำแหน่ง (หรือตามชื่อ)
  • พารามิเตอร์หลัง_p=__named_only_startจะต้องระบุด้วยชื่อเท่านั้น (เว้นแต่__named_only_startจะได้รับและใช้ความรู้เกี่ยวกับอ็อบเจกต์ Sentinel พิเศษ)

ข้อดี:

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

จุดด้อย:

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

โปรดทราบว่าคำตอบนี้ใช้ได้กับ Python 2 เท่านั้น Python 3 ใช้กลไกที่รองรับภาษาที่คล้ายกัน แต่สวยงามมากซึ่งอธิบายไว้ในคำตอบอื่น ๆ

ฉันพบว่าเมื่อฉันเปิดใจและคิดเกี่ยวกับเรื่องนี้ไม่มีคำถามหรือการตัดสินใจของคนอื่นดูโง่โง่หรือโง่เขลา ค่อนข้างตรงกันข้าม: ฉันมักจะเรียนรู้มากมาย


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

@ เอริก: ฉันต้องการการตรวจสอบแบบคงที่ แต่คุณพูดถูก: นั่นจะไม่ใช่ Python เลย แม้ว่าจะไม่ใช่จุดชี้ขาด แต่โครงสร้าง "*" ของ Python 3 ก็ถูกตรวจสอบแบบไดนามิกเช่นกัน ขอบคุณสำหรับความคิดเห็นของคุณ
Mario Rossi

นอกจากนี้หากคุณตั้งชื่อตัวแปรโมดูล_named_only_startจะเป็นไปไม่ได้ที่จะอ้างอิงจากโมดูลภายนอกซึ่งจะใช้ pro และ con (ขีดล่างชั้นนำเดียวที่ขอบเขตโมดูลเป็นส่วนตัว IIRC)
Eric

เกี่ยวกับการตั้งชื่อของแมวมองเราอาจมีทั้ง a __named_only_startและ a named_only_start(ไม่มีขีดล่างเริ่มต้น) อันที่สองเพื่อระบุว่าโหมดที่ตั้งชื่อนั้นเป็น "แนะนำ" แต่ไม่ถึงระดับที่ "ได้รับการส่งเสริมอย่างจริงจัง" (เนื่องจากเป็นโหมดสาธารณะและ อื่น ๆ ไม่ได้) เกี่ยวกับ "ความเป็นส่วนตัว" ของการ_namesเริ่มต้นด้วยเครื่องหมายขีดล่างภาษานั้นไม่ได้บังคับใช้อย่างจริงจังมากนัก: สามารถหลีกเลี่ยงได้ง่ายโดยการใช้การนำเข้าเฉพาะ (ที่ไม่ใช่ *) หรือชื่อที่มีคุณสมบัติเหมาะสม นี่คือสาเหตุที่เอกสาร Python หลายฉบับชอบใช้คำว่า "non-public" แทน "private"
Mario Rossi

6

คุณสามารถทำได้ด้วยวิธี ที่ใช้ได้ทั้งใน Python 2 และ Python 3โดยสร้างอาร์กิวเมนต์คำหลักแรก "ปลอม" ด้วยค่าเริ่มต้นที่จะไม่เกิดขึ้น "ตามธรรมชาติ" อาร์กิวเมนต์คำหลักนั้นสามารถนำหน้าด้วยอาร์กิวเมนต์อย่างน้อยหนึ่งรายการโดยไม่มีค่า:

_dummy = object()

def info(object, _kw=_dummy, spacing=10, collapse=1):
    if _kw is not _dummy:
        raise TypeError("info() takes 1 positional argument but at least 2 were given")

สิ่งนี้จะช่วยให้:

info(odbchelper)        
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

แต่ไม่:

info(odbchelper, 12)                

หากคุณเปลี่ยนฟังก์ชันเป็น:

def info(_kw=_dummy, spacing=10, collapse=1):

ดังนั้นอาร์กิวเมนต์ทั้งหมดจะต้องมีคีย์เวิร์ดและinfo(odbchelper)จะไม่ทำงานอีกต่อไป

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

ดังนั้นจึงไม่จำเป็นต้องเปลี่ยนกลับไปใช้def(**kwargs)และสูญเสียข้อมูลลายเซ็นในตัวแก้ไขอัจฉริยะของคุณ สัญญาทางสังคมของคุณคือการให้ข้อมูลบางอย่างโดยบังคับให้ (บางส่วน) ต้องการคำหลักลำดับที่นำเสนอกลายเป็นสิ่งที่ไม่เกี่ยวข้อง


2

อัปเดต:

ฉันตระหนักว่าการใช้**kwargsจะไม่สามารถแก้ปัญหาได้ หากโปรแกรมเมอร์ของคุณเปลี่ยนอาร์กิวเมนต์ของฟังก์ชันตามที่ต้องการตัวอย่างเช่นอาจเปลี่ยนฟังก์ชันเป็นดังนี้

def info(foo, **kwargs):

และรหัสเก่าจะพังอีกครั้ง (เพราะตอนนี้ทุกการเรียกใช้ฟังก์ชันจะต้องมีอาร์กิวเมนต์แรก)

มันขึ้นอยู่กับสิ่งที่ไบรอันพูดจริงๆ


(... ) ผู้คนอาจเพิ่มพารามิเตอร์ระหว่างspacingและcollapse(... )

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

(... ) บางครั้งก็ไม่ชัดเจนเสมอไปว่าจะต้องทำอะไร

โดยการดูลายเซ็นของฟังก์ชัน (เช่นdef info(object, spacing=10, collapse=1)) เราควรเห็นทันทีว่าทุกอาร์กิวเมนต์ที่ไม่มีค่าดีฟอลต์มีผลบังคับใช้
อะไรอาร์กิวเมนต์สำหรับควรไปลงใน docstring


คำตอบเก่า (เก็บไว้เพื่อความสมบูรณ์) :

นี่อาจไม่ใช่วิธีแก้ปัญหาที่ดี:

คุณสามารถกำหนดฟังก์ชันด้วยวิธีนี้:

def info(**kwargs):
    ''' Some docstring here describing possible and mandatory arguments. '''
    spacing = kwargs.get('spacing', 15)
    obj = kwargs.get('object', None)
    if not obj:
       raise ValueError('object is needed')

kwargsคือพจนานุกรมที่มีอาร์กิวเมนต์คำหลักใด ๆ คุณสามารถตรวจสอบได้ว่ามีอาร์กิวเมนต์ที่จำเป็นหรือไม่และหากไม่มีให้เพิ่มข้อยกเว้น

ข้อเสียคือมันอาจจะไม่ชัดเจนอีกต่อไปข้อโต้แย้งใดที่เป็นไปได้ แต่ด้วย docstring ที่เหมาะสมก็น่าจะใช้ได้


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

ไม่มีคำตอบที่แท้จริงในคำตอบนี้!
ฟิลิป

2

อาร์กิวเมนต์เฉพาะคีย์เวิร์ด python3 ( *) สามารถจำลองใน python2.x ด้วย**kwargs

พิจารณารหัส python3 ต่อไปนี้:

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)

และพฤติกรรมของมัน:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default='hi')
1 hi default
>>> f(1, no_default='hi', has_default='hello')
1 hi hello
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'

สิ่งนี้สามารถจำลองได้โดยใช้สิ่งต่อไปนี้โปรดทราบว่าฉันได้ใช้เสรีภาพในการเปลี่ยนTypeErrorไปใช้KeyErrorในกรณี "อาร์กิวเมนต์ที่ระบุชื่อที่จำเป็น" มันจะไม่เป็นการทำงานมากเกินไปที่จะทำให้เป็นประเภทข้อยกเว้นเดียวกันเช่นกัน

def f(pos_arg, **kwargs):
    no_default = kwargs.pop('no_default')
    has_default = kwargs.pop('has_default', 'default')
    if kwargs:
        raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))

    print(pos_arg, no_default, has_default)

และพฤติกรรม:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default='hi')
(1, 'hi', 'default')
>>> f(1, no_default='hi', has_default='hello')
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat

สูตรนี้ใช้งานได้เท่าเทียมกันใน python3.x แต่ควรหลีกเลี่ยงหากคุณเป็น python3.x เท่านั้น


อ่าkwargs.pop('foo')สำนวน Python 2 งั้นเหรอ? ฉันต้องการอัปเดตรูปแบบการเข้ารหัสของฉัน ฉันยังคงใช้แนวทางนี้ใน Python 3 🤔
Neil

0

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

def foo(**args):
   print args

foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given)
foo(hello = 1, goodbye = 2) # Works fine.

1
ไม่เพียง แต่คุณต้องมีการเพิ่มการตรวจสอบคำหลัก foo(**kwargs)แต่คิดว่าเกี่ยวกับผู้บริโภคที่รู้ว่าพวกเขามีการโทรวิธีการที่มีลายเซ็นที่ ฉันต้องผ่านอะไรไปบ้าง? foo(killme=True, when="rightnowplease")
Dagrooms

มันขึ้นอยู่กับจริงๆ พิจารณาdict.
Noufal Ibrahim

0

ดังคำตอบอื่น ๆ กล่าวว่าการเปลี่ยนลายเซ็นฟังก์ชันเป็นความคิดที่ไม่ดี เพิ่มพารามิเตอร์ใหม่ต่อท้ายหรือแก้ไขผู้เรียกทุกคนหากมีการแทรกอาร์กิวเมนต์

หากคุณยังต้องการทำเช่นนั้นให้ใช้ฟังก์ชันมัณฑนากรและฟังก์ชันinspargspec จะใช้ในลักษณะนี้:

@require_named_args
def info(object, spacing=10, collapse=1):
    ....

การนำไปใช้งาน require_named_argsเป็นแบบฝึกหัดสำหรับผู้อ่าน

ฉันจะไม่รำคาญ มันจะช้าทุกครั้งที่เรียกใช้ฟังก์ชันและคุณจะได้ผลลัพธ์ที่ดีขึ้นจากการเขียนโค้ดอย่างรอบคอบมากขึ้น


-1

คุณสามารถใช้ตัว**ดำเนินการ:

def info(**kwargs):

วิธีนี้ผู้คนถูกบังคับให้ใช้พารามิเตอร์ที่มีชื่อ


2
และไม่รู้ว่าจะเรียกวิธีการของคุณโดยไม่อ่านโค้ดของคุณได้อย่างไรเพิ่มภาระทางความคิดให้กับผู้บริโภคของคุณ :(
Dagrooms

เนื่องจากเหตุผลที่กล่าวมานี้เป็นการปฏิบัติที่ไม่ดีจริงๆและควรหลีกเลี่ยง
David S.

-1
def cheeseshop(kind, *arguments, **keywords):

ใน python ถ้าใช้ * args ซึ่งหมายความว่าคุณสามารถส่ง n-number ของอาร์กิวเมนต์สำหรับพารามิเตอร์นี้ซึ่งจะเป็นรายการภายในฟังก์ชันเพื่อเข้าถึง

และหากใช้ ** kw ซึ่งหมายถึงอาร์กิวเมนต์คำหลักที่สามารถเข้าถึงได้ในรูปแบบ dict - คุณสามารถส่ง n-number ของ kw args และหากคุณต้องการ จำกัด ผู้ใช้นั้นจะต้องป้อนลำดับและอาร์กิวเมนต์ตามลำดับอย่าใช้ * และ ** - (วิธีไพโธนิกในการจัดหาโซลูชันทั่วไปสำหรับสถาปัตยกรรมขนาดใหญ่ ... )

หากคุณต้องการ จำกัด ฟังก์ชันของคุณด้วยค่าเริ่มต้นคุณสามารถตรวจสอบภายในได้

def info(object, spacing, collapse)
  spacing = spacing or 10
  collapse = collapse or 1

จะเกิดอะไรขึ้นถ้าต้องการให้ระยะห่างเป็น 0? (คำตอบคุณจะได้รับ 10) คำตอบนี้ผิดเหมือนกับคำตอบอื่น ๆ ** kwargs ด้วยเหตุผลเดียวกันทั้งหมด
ฟิลิป

-2

ฉันไม่เข้าใจว่าทำไมโปรแกรมเมอร์ถึงเพิ่มพารามิเตอร์ระหว่างสองตัวอื่นในตอนแรก

หากคุณต้องการให้พารามิเตอร์ฟังก์ชันใช้กับชื่อ (เช่น info(spacing=15, object=odbchelper) ) ก็ไม่สำคัญว่าจะกำหนดลำดับที่ใดดังนั้นคุณอาจใส่พารามิเตอร์ใหม่ลงท้ายด้วย

หากคุณต้องการให้คำสั่งมีความสำคัญคุณไม่สามารถคาดหวังสิ่งใดที่จะทำงานได้หากคุณเปลี่ยน!


2
สิ่งนี้ไม่ตอบคำถาม ไม่ว่าจะเป็นความคิดที่ดีหรือไม่ก็ตามก็อาจมีคนทำอยู่ดี
Graeme Perrow

1
ดังที่ Graeme กล่าวถึงจะมีคนทำอยู่ดี นอกจากนี้หากคุณกำลังเขียนไลบรารีเพื่อให้ผู้อื่นใช้การบังคับ (python 3 เท่านั้น) การส่งผ่านอาร์กิวเมนต์เฉพาะคีย์เวิร์ดจะช่วยให้มีความยืดหยุ่นมากขึ้นเมื่อคุณต้อง refactor API ของคุณ
s0undt3ch
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.