Thuộc tính hàm Python - sử dụng và lạm dụng [đã đóng]


195

Không nhiều người biết đến tính năng này, nhưng các hàm (và phương thức) của Python có thể có các thuộc tính . Hãy chứng kiến:

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

Việc sử dụng và lạm dụng tính năng này trong Python là gì? Một cách sử dụng tốt mà tôi biết là việc sử dụng chuỗi docY của PLY để liên kết quy tắc cú pháp với một phương thức. Nhưng những gì về thuộc tính tùy chỉnh? Có những lý do tốt để sử dụng chúng?


3
Kiểm tra PEP 232 .
dùng140352

2
Đây có phải là rất đáng ngạc nhiên? Nói chung, các đối tượng Python hỗ trợ các thuộc tính đặc biệt. Tất nhiên, một số thì không, đặc biệt là những người có kiểu dựng sẵn. Đối với tôi, những người không ủng hộ điều này dường như là ngoại lệ, không phải là quy tắc.
mã allyour


2
@GrijeshChauhan Tôi đã đến câu hỏi này sau khi xem những tài liệu này!
Alexander Suraphel

5
Đáng tiếc là điều này đã bị đóng, tôi muốn thêm rằng bạn có thể đính kèm bất kỳ trường hợp ngoại lệ tùy chỉnh nào mà chức năng có thể nêu ra, để cung cấp quyền truy cập dễ dàng khi bắt nó trong mã gọi. Tôi sẽ cung cấp một ví dụ minh họa, nhưng đó là cách tốt nhất để trả lời.
Will Hardy

Câu trả lời:


153

Tôi thường sử dụng các thuộc tính chức năng như lưu trữ cho các chú thích. Giả sử tôi muốn viết, theo kiểu C # (chỉ ra rằng một phương thức nhất định phải là một phần của giao diện dịch vụ web)

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

sau đó tôi có thể định nghĩa

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

Sau đó, khi có cuộc gọi dịch vụ web đến, tôi tìm phương thức, kiểm tra xem hàm bên dưới có thuộc tính is_webmethod (giá trị thực không liên quan) và từ chối dịch vụ nếu phương thức vắng mặt hoặc không được gọi qua web.


2
Bạn có nghĩ rằng có những mặt trái của vấn đề này? ví dụ: Nếu hai thư viện cố gắng viết cùng một thuộc tính ad-hoc thì sao?
mã allyour

18
Tôi đã nghĩ làm chính xác điều này. Rồi tôi dừng lại. "Đây có phải là một ý tưởng tồi?" Tôi tự hỏi. Sau đó, tôi lang thang đến SO. Sau một vài lần loay hoay, tôi tìm thấy câu hỏi / câu trả lời này. Vẫn không chắc chắn nếu đây là một ý tưởng tốt.
mã allyour

7
Đây chắc chắn là cách sử dụng hợp pháp nhất các thuộc tính hàm của tất cả các câu trả lời (kể từ tháng 11 năm 2012). Hầu hết (nếu không phải tất cả) các câu trả lời khác sử dụng các thuộc tính hàm để thay thế cho các biến toàn cục; tuy nhiên, họ KHÔNG thoát khỏi trạng thái toàn cầu, đó chính xác là vấn đề với các biến toàn cầu. Điều này là khác nhau, bởi vì một khi giá trị được đặt, nó không thay đổi; nó là hằng số Một hậu quả tốt đẹp của điều này là bạn không gặp phải các vấn đề về đồng bộ hóa vốn có với các biến toàn cục. Có, bạn có thể cung cấp đồng bộ hóa của riêng mình, nhưng đó là điểm: nó không tự động an toàn.
mã allyour

Thật vậy, tôi nói, miễn là thuộc tính không thay đổi hành vi của hàm được đề cập, thì tốt. So sánh với.__doc__
Dima Tisnek

Cách tiếp cận này cũng có thể được sử dụng để đính kèm mô tả đầu ra cho chức năng được trang trí, điều còn thiếu trong python 2. *.
Juh_

125

Tôi đã sử dụng chúng như các biến tĩnh cho một hàm. Ví dụ: đã cho mã C sau:

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

Tôi có thể thực hiện chức năng tương tự trong Python:

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

Điều này chắc chắn sẽ rơi vào cuối "lạm dụng" của quang phổ.


2
Hấp dẫn. Có những cách khác để thực hiện các biến tĩnh trong python?
Eli Bendersky

4
-1, điều này sẽ được thực hiện với một trình tạo trong python.

124
Đó là một lý do khá kém để đánh giá thấp câu trả lời này, điều này thể hiện sự tương đồng giữa C và Python, không ủng hộ cách tốt nhất có thể để viết hàm đặc biệt này.
Robert Rossney

3
@RobertRossney Nhưng nếu máy phát điện hoạt động, thì đây là cách sử dụng kém các thuộc tính hàm. Nếu vậy, thì đây là một lạm dụng. Tuy nhiên, không chắc chắn có nên upvote lạm dụng hay không, vì câu hỏi cũng yêu cầu những điều đó: P
allyourcode

1
Chắc chắn có vẻ như là một sự lạm dụng, theo PEP 232, cảm ơn @ user140352, và nhận xét của hop và câu trả lời SO này
hobs

52

Bạn có thể thực hiện các đối tượng theo cách JavaScript ... Không có nghĩa gì nhưng nó hoạt động;)

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

36
+1 Một ví dụ điển hình về việc lạm dụng tính năng này, đây là một trong những điều mà câu hỏi yêu cầu.
Michael Dunn

1
Điều này khác với câu trả lời của mipadi như thế nào? Có vẻ là điều tương tự, ngoại trừ thay vì int, giá trị thuộc tính là một hàm.
mã allyour

def test()thực sự cần thiết?
Keerthana Mitchhakaran

15

Tôi sử dụng chúng một cách tiết kiệm, nhưng chúng có thể khá thuận tiện:

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

Bây giờ tôi có thể sử dụng logtrong suốt mô-đun của mình và chuyển hướng đầu ra chỉ bằng cách cài đặt log.logfile. Có rất nhiều cách khác để thực hiện điều đó, nhưng đây là cách đơn giản và nhẹ nhàng. Và mặc dù nó có mùi buồn cười ngay lần đầu tiên tôi làm điều đó, tôi đã tin rằng nó có mùi tốt hơn là có một logfilebiến toàn cầu .


7
re mùi: Điều này không thoát khỏi logfile toàn cầu mặc dù. Nó chỉ thu nhận nó trong một chức năng toàn cầu khác.
mã allyour

2
@allyourcode: Nhưng nó có thể giúp tránh xung đột tên nếu bạn phải có một loạt các logfile toàn cầu cho các chức năng khác nhau trong cùng một mô-đun.
firegurafiku

11

Các thuộc tính chức năng có thể được sử dụng để viết các bao đóng trọng lượng nhẹ bao bọc mã và dữ liệu liên quan với nhau:

#!/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,0034004784

2.00644397736

3.01593494415


3
Tại sao chức năng đẩy khi chúng ta có các lớp tiện dụng? Và đừng quên các lớp có thể mô phỏng một hàm.
muhuk

1
cũng time.sleep(1)tốt hơnos.system('sleep 1')
Boris Gorelik

3
@bgbg Đúng, mặc dù ví dụ này không phải là về việc ngủ.
mã allyour

Đây chắc chắn là một sự lạm dụng; việc sử dụng các chức năng ở đây là hoàn toàn vô cớ. muhuk hoàn toàn chính xác: các lớp học là một giải pháp tốt hơn.
mã allyour

1
Tôi cũng sẽ hỏi, "lợi thế của việc này so với một lớp học là gì?" để chống lại nhược điểm của việc này không rõ ràng đối với nhiều lập trình viên Python.
cjs

6

Tôi đã tạo trình trang trí trợ giúp này để dễ dàng đặt các thuộc tính chức năng:

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

Trường hợp sử dụng là tạo một tập hợp các nhà máy và truy vấn kiểu dữ liệu mà họ có thể tạo ở cấp độ hàm.
Ví dụ (rất ngu ngốc):

@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

Làm thế nào để trang trí giúp? Tại sao không chỉ đặt factory1.datatype=listngay bên dưới tuyên bố như trang trí kiểu cũ?
Ceasar Bautista

2
2 điểm khác biệt chính: kiểu dáng, nhiều thuộc tính dễ đặt hơn. Theo ý kiến ​​của tôi, bạn chắc chắn có thể đặt thành một thuộc tính nhưng có nhiều chi tiết theo quan điểm của tôi và bạn cũng có cơ hội mở rộng trình trang trí để xử lý thêm (như mặc định được xác định ở một nơi thay vì tất cả các địa điểm sử dụng chức năng hoặc phải gọi một chức năng bổ sung sau khi các thuộc tính được thiết lập). Có nhiều cách khác để đạt được tất cả những kết quả này, tôi chỉ thấy điều này sạch sẽ hơn, nhưng rất vui khi thay đổi suy nghĩ của mình;)
DiogoNeves

Cập nhật nhanh: với Python 3 bạn cần sử dụng items()thay vì iteritems().
Scott có nghĩa là

4

Đôi khi tôi sử dụng một thuộc tính của hàm để lưu các giá trị đã được tính toán. Bạn cũng có thể có một trang trí chung chung khái quát phương pháp này. Hãy nhận biết các vấn đề tương tranh và tác dụng phụ của các chức năng đó!


Tôi thích ý tưởng này! Một mẹo phổ biến hơn để lưu các giá trị được tính toán vào bộ đệm là sử dụng một dict làm giá trị mặc định của một thuộc tính mà người gọi không bao giờ có ý định cung cấp - vì Python chỉ đánh giá một lần khi xác định hàm, bạn có thể lưu trữ dữ liệu trong đó và giữ nó xung quanh. Mặc dù việc sử dụng các thuộc tính chức năng có thể ít rõ ràng hơn, nhưng tôi cảm thấy ít bị hack hơn.
Soren Bjornstad

1

Tôi luôn luôn cho rằng lý do duy nhất có thể xảy ra là vì vậy có một vị trí hợp lý để đặt chuỗi doc hoặc các thứ khác như vậy. Tôi biết nếu tôi đã sử dụng nó cho bất kỳ mã sản xuất nào, nó sẽ gây nhầm lẫn cho hầu hết những ai đọc nó.


1
Tôi đồng ý với quan điểm chính của bạn về điều này rất có thể gây nhầm lẫn, nhưng lại hiểu: Có, nhưng tại sao các hàm có thuộc tính AD-HOC? Có thể có một tập hợp các thuộc tính cố định, một thuộc tính để giữ chuỗi doc.
mã allyour

@allyourcode Có trường hợp chung thay vì các trường hợp đặc biệt được thiết kế thành ngôn ngữ giúp mọi thứ đơn giản hơn và tăng khả năng tương thích với các phiên bản cũ hơn của Python. (Ví dụ, mã mà bộ / thao túng docstrings sẽ vẫn làm việc với một phiên bản của Python mà không làm docstrings, miễn là nó xử lý các trường hợp thuộc tính không tồn tại.)
CJS
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.