คุณลักษณะของฟังก์ชั่น Python - การใช้และการใช้ในทางที่ผิด [ปิด]


196

มีคนไม่มากที่รู้คุณลักษณะนี้ แต่ฟังก์ชันของ Python (และวิธีการ) สามารถมีคุณสมบัติได้ ดูเถิด:

>>> def foo(x):
...     pass
...     
>>> foo.score = 10
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11

มีการใช้และการละเมิดคุณลักษณะนี้ใน Python อย่างไร สิ่งที่ดีอย่างหนึ่งที่ฉันรู้คือการใช้งานของPLYเพื่อเชื่อมโยงกฎไวยากรณ์กับเมธอด แต่สิ่งที่เกี่ยวกับคุณลักษณะที่กำหนดเอง? มีเหตุผลที่ดีที่จะใช้หรือไม่


3
ตรวจสอบPEP 232
user140352

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


2
@GrijeshChauhan ฉันมาที่คำถามนี้หลังจากเห็นเอกสารเหล่านี้!
Alexander Suraphel

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

คำตอบ:


154

ฉันมักจะใช้คุณลักษณะของฟังก์ชั่นเป็นที่เก็บข้อมูลสำหรับคำอธิบายประกอบ สมมติว่าฉันต้องการเขียนในรูปแบบของ C # (ระบุว่าวิธีการบางอย่างควรเป็นส่วนหนึ่งของส่วนต่อประสานบริการเว็บ)

class Foo(WebService):
    @webmethod
    def bar(self, arg1, arg2):
         ...

จากนั้นฉันสามารถกำหนด

def webmethod(func):
    func.is_webmethod = True
    return func

จากนั้นเมื่อมีการเรียกใช้บริการเว็บเซอร์ฉันค้นหาวิธีตรวจสอบว่าฟังก์ชั่นพื้นฐานมีคุณสมบัติ is_webmethod (ค่าจริงไม่เกี่ยวข้อง) และปฏิเสธบริการหากวิธีนั้นขาดหรือไม่ควรเรียกผ่านเว็บ


2
คุณคิดว่ามีข้อเสียอยู่หรือเปล่า? เช่นถ้าห้องสมุดสองแห่งพยายามเขียนคุณสมบัติ ad-hoc เดียวกัน
allyourcode

18
ฉันกำลังคิดที่จะทำสิ่งนี้ จากนั้นฉันก็หยุดตัวเอง "นี่เป็นความคิดที่ไม่ดีเหรอ?" ฉันสงสัย. จากนั้นฉันก็เดินไปทางนั้น ฉันพบคำถาม / คำตอบนี้ ยังไม่แน่ใจว่านี่เป็นความคิดที่ดีหรือไม่
allyourcode

7
นี่เป็นการใช้งานฟังก์ชั่นฟังก์ชั่นของคำตอบทั้งหมดอย่างถูกต้องที่สุด (ณ เดือนพฤศจิกายน 2012) คำตอบอื่น ๆ ส่วนใหญ่ (ถ้าไม่ใช่ทั้งหมด) ใช้คุณลักษณะของฟังก์ชั่นแทนตัวแปรทั่วโลก; อย่างไรก็ตามพวกเขาไม่ได้กำจัดสถานะโลกซึ่งเป็นปัญหากับตัวแปรทั่วโลก สิ่งนี้แตกต่างกันเนื่องจากเมื่อตั้งค่าแล้วจะไม่เปลี่ยนแปลง มันคงที่ ผลลัพธ์ที่ดีของสิ่งนี้คือคุณไม่พบปัญหาการซิงโครไนซ์ซึ่งมีอยู่ในตัวแปรโกลบอล ใช่คุณสามารถจัดทำข้อมูลให้ตรงกันของคุณเอง แต่นั่นคือประเด็น: มันไม่ปลอดภัยโดยอัตโนมัติ
allyourcode

อันที่จริงฉันพูดตราบใดที่คุณลักษณะไม่เปลี่ยนพฤติกรรมของฟังก์ชันที่เป็นปัญหา เปรียบเทียบกับ.__doc__
Dima Tisnek

วิธีการนี้ยังสามารถใช้เพื่อแนบคำอธิบายผลลัพธ์ไปยังฟังก์ชั่นการตกแต่งที่ขาดหายไปในไพ ธ อน 2 *
Juh_

126

ฉันใช้มันเป็นตัวแปรคงที่สำหรับฟังก์ชั่น ตัวอย่างเช่นได้รับรหัส C ต่อไปนี้:

int fn(int i)
{
    static f = 1;
    f += i;
    return f;
}

ฉันสามารถใช้ฟังก์ชั่นคล้ายกันใน Python:

def fn(i):
    fn.f += i
    return fn.f
fn.f = 1

นี่จะเป็นจุดสิ้นสุดของ "การละเมิด" อย่างแน่นอน


2
น่าสนใจ มีวิธีอื่นในการใช้ตัวแปรคงที่ในไพ ธ อนหรือไม่
Eli Bendersky

4
-1 สิ่งนี้จะถูกนำมาใช้กับเครื่องกำเนิดในหลาม

124
นั่นเป็นเหตุผลที่น่าสงสารที่จะลบคำตอบนี้ซึ่งแสดงให้เห็นถึงการเปรียบเทียบระหว่าง C และ Python ไม่สนับสนุนวิธีที่ดีที่สุดในการเขียนฟังก์ชันนี้
Robert Rossney

3
@RobertRossney แต่ถ้ากำเนิดเป็นวิธีที่จะไปแล้วนี่คือการใช้งานที่ไม่ดีของคุณลักษณะฟังก์ชั่น ถ้าเป็นเช่นนั้นนี่เป็นการละเมิด ไม่แน่ใจว่าจะยกเลิกการละเมิดหรือไม่เนื่องจากคำถามถามถึงสิ่งเหล่านั้นด้วยเช่นกัน: P
allyourcode

1
แน่นอนดูเหมือนว่าการละเมิดต่อ PEP 232 ขอบคุณ @ user140352 และความคิดเห็นปฮอปและคำตอบดังนั้นนี้
เตาแก๊ส

53

คุณสามารถทำวัตถุด้วยวิธี JavaScript ... มันไม่สมเหตุสมผล แต่ใช้งานได้)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo

36
+1 ตัวอย่างที่ดีของการใช้คุณลักษณะนี้ในทางที่ผิดซึ่งเป็นหนึ่งในสิ่งที่คำถามต้องการ
Michael Dunn

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

เป็นdef test()สิ่งที่จำเป็นจริงๆ?
กีรนาปราการะรัน

15

ฉันใช้พวกเขาเท่าที่จำเป็น แต่พวกเขาค่อนข้างสะดวก:

def log(msg):
   log.logfile.write(msg)

ตอนนี้ผมสามารถใช้ตลอดทั้งโมดูลของฉันและเปลี่ยนเส้นทางออกได้ง่ายๆโดยการตั้งค่าlog log.logfileมีวิธีอื่น ๆ อีกมากมายที่จะทำให้สำเร็จได้ แต่สิ่งนี้มีน้ำหนักเบาและสกปรกง่าย และในขณะที่มันมีกลิ่นตลกครั้งแรกที่ฉันทำฉันมาเชื่อว่ามันมีกลิ่นที่ดีกว่าการมีlogfileตัวแปรทั่วโลก


7
กำลังได้กลิ่น: นี่ไม่ได้เป็นการกำจัดไฟล์บันทึกส่วนกลาง มันแค่กระรอกมันออกไปในอีกโลกหนึ่งฟังก์ชั่นบันทึก
allyourcode

2
@ alt code: แต่มันสามารถช่วยหลีกเลี่ยงการปะทะกันของชื่อหากคุณต้องมี logfiles ทั่วโลกสำหรับฟังก์ชั่นต่าง ๆ ในโมดูลเดียวกัน
firegurafiku

11

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

#!/usr/bin/env python

SW_DELTA = 0
SW_MARK  = 1
SW_BASE  = 2

def stopwatch():
   import time

   def _sw( action = SW_DELTA ):

      if action == SW_DELTA:
         return time.time() - _sw._time

      elif action == SW_MARK:
         _sw._time = time.time()
         return _sw._time

      elif action == SW_BASE:
         return _sw._time

      else:
         raise NotImplementedError

   _sw._time = time.time() # time of creation

   return _sw

# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()

1.00934004784

2.00644397736

3.01593494415


3
ทำไมต้องกดฟังก์ชั่นเมื่อเรามีชั้นเรียนที่สะดวก? และอย่าลืมคลาสที่สามารถเลียนแบบฟังก์ชันได้
muhuk

1
ยังtime.sleep(1)ดีกว่าos.system('sleep 1')
Boris Gorelik

3
@bgbg จริงแม้ว่าตัวอย่างนี้ไม่เกี่ยวกับการนอน
allyourcode

นี่เป็นการละเมิดอย่างแน่นอน การใช้ฟังก์ชั่นของที่นี่นั้นฟรีทั้งหมด muhuk พูดถูกต้อง: คลาสเป็นทางออกที่ดีกว่า
allyourcode

1
ฉันจะถามว่า "ข้อดีของการเรียนแบบนี้คืออะไร?" เพื่อตอบโต้ข้อเสียของการไม่เห็นได้ชัดกับโปรแกรมเมอร์ Python หลายคน
cjs

6

ฉันได้สร้างมัณฑนากรผู้ช่วยนี้เพื่อตั้งค่าคุณลักษณะของฟังก์ชันได้อย่างง่ายดาย:

def with_attrs(**func_attrs):
    """Set attributes in the decorated function, at definition time.
    Only accepts keyword arguments.
    E.g.:
        @with_attrs(counter=0, something='boing')
        def count_it():
            count_it.counter += 1
        print count_it.counter
        print count_it.something
        # Out:
        # >>> 0
        # >>> 'boing'
    """
    def attr_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)

        for attr, value in func_attrs.iteritems():
            setattr(wrapper, attr, value)

        return wrapper

    return attr_decorator

กรณีใช้งานคือการสร้างคอลเลกชันของโรงงานและสอบถามประเภทข้อมูลที่พวกเขาสามารถสร้างในระดับเมตาฟังก์ชั่น
ตัวอย่าง (หนึ่งที่โง่มาก):

@with_attrs(datatype=list)
def factory1():
    return [1, 2, 3]

@with_attrs(datatype=SomeClass)
def factory2():
    return SomeClass()

factories = [factory1, factory2]

def create(datatype):
    for f in factories:
        if f.datatype == datatype:
            return f()
    return None

มัณฑนากรช่วยได้อย่างไร ทำไมไม่เพียงแค่ตั้งค่าfactory1.datatype=listด้านล่างของการประกาศเหมือนนักตกแต่งสไตล์เก่า
Ceasar Bautista

2
2 ความแตกต่างที่สำคัญ: สไตล์การตั้งค่าหลาย ๆ แอ็ตทริบิวต์ง่ายขึ้น คุณสามารถกำหนดเป็นแอตทริบิวต์ได้ แต่รับ verbose ด้วยคุณสมบัติหลายอย่างในความคิดของฉันและคุณยังมีโอกาสที่จะขยาย decorator สำหรับการประมวลผลเพิ่มเติม (เช่นมีการกำหนดค่าเริ่มต้นไว้ในที่เดียวแทนที่จะเป็นสถานที่ทั้งหมดที่ใช้ฟังก์ชันหรือ เรียกใช้ฟังก์ชันพิเศษหลังจากตั้งค่าแอตทริบิวต์แล้ว) มีวิธีอื่น ๆ เพื่อให้ได้ผลลัพธ์ทั้งหมดเหล่านี้ฉันเพิ่งพบว่ามันสะอาดขึ้น แต่มีความสุขที่จะเปลี่ยนใจ;)
DiogoNeves

ปรับปรุงด่วน: กับงูหลาม 3 คุณจำเป็นต้องใช้แทนitems() iteritems()
Scott หมายถึง

4

บางครั้งฉันใช้คุณลักษณะของฟังก์ชั่นสำหรับแคชค่าที่คำนวณได้แล้ว คุณสามารถมีมัณฑนากรทั่วไปที่สรุปแนวทางนี้ ระวังปัญหาที่เกิดขึ้นพร้อมกันและผลข้างเคียงของฟังก์ชั่นดังกล่าว!


ฉันชอบความคิดนี้! เคล็ดลับทั่วไปสำหรับการแคชค่าที่คำนวณได้คือการใช้ dict เป็นค่าเริ่มต้นของคุณลักษณะที่ผู้เรียกไม่ได้ตั้งใจให้ - เนื่องจาก Python ประเมินว่าเพียงครั้งเดียวเมื่อกำหนดฟังก์ชันคุณสามารถเก็บข้อมูลไว้ในนั้น รอบ ในขณะที่ใช้ฟังก์ชั่นฟังก์ชั่นอาจจะเห็นได้ชัดน้อยลง
Soren

1

ฉันมักจะสันนิษฐานว่าเหตุผลเดียวที่เป็นไปได้คือมีเหตุผลที่จะวาง doc-string หรือสิ่งอื่น ๆ ฉันรู้ว่าถ้าฉันใช้มันสำหรับรหัสการผลิตใด ๆ มันจะสร้างความสับสนให้มากที่สุดที่อ่านมัน


1
ฉันเห็นด้วยกับประเด็นหลักของคุณเกี่ยวกับเรื่องนี้ซึ่งอาจทำให้เกิดความสับสน แต่มีเอกสารใหม่: ใช่ แต่ทำไมฟังก์ชั่นถึงมีคุณลักษณะ AD-HOC อาจมีชุดแอตทริบิวต์คงที่ชุดหนึ่งสำหรับเก็บสตริงสตริง
allyourcode

@allyourcode การมีเคสทั่วไปแทนที่จะเป็น ad-hoc เคสเฉพาะที่ออกแบบเป็นภาษาทำให้สิ่งต่าง ๆ ง่ายขึ้นและเพิ่มความเข้ากันได้กับ Python เวอร์ชันเก่า (เช่นรหัสที่กำหนด / จัดการกับ docstrings จะยังคงใช้งานได้กับเวอร์ชันของ Python ที่ไม่ได้ทำเอกสารตราบใดที่มันจัดการกับกรณีที่ไม่มีแอตทริบิวต์นั้นอยู่)
cjs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.