Atrybuty funkcji Pythona - wykorzystuje i wykorzystuje [zamknięte]

196

Niewielu jest świadomych tej funkcji, ale funkcje (i metody) Pythona mogą mieć atrybuty . Ujrzeć:

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

Jakie są możliwe zastosowania i nadużycia tej funkcji w Pythonie? Jednym z dobrych zastosowań, o których jestem świadomy, jest użycie przez PLY metody docstring do powiązania reguły składni z metodą. Ale co z niestandardowymi atrybutami? Czy istnieją dobre powody, aby z nich korzystać?

Eli Bendersky
źródło
3
Sprawdź PEP 232 .
user140352,
2
Czy to bardzo zaskakujące? Ogólnie obiekty Python obsługują atrybuty ad-hoc. Oczywiście niektóre nie, szczególnie te z wbudowanym typem. Dla mnie ci, którzy tego nie popierają, wydają się wyjątkami, a nie regułą.
allyourcode
3
Jedna aplikacja w Django: Dostosuj listę zmian administratora
Grijesh Chauhan
2
@GrijeshChauhan Doszedłem do tego pytania po obejrzeniu tych dokumentów!
Alexander Suraphel
5
Szkoda, że ​​to jest zamknięte, chciałem dodać, że możesz dołączyć niestandardowe wyjątki, które może wywołać funkcja, aby zapewnić łatwy dostęp podczas przechwytywania jej w kodzie wywołującym. Podałbym przykładowy przykład, ale najlepiej zrobić to w odpowiedzi.
Will Hardy,

Odpowiedzi:

154

Zazwyczaj używam atrybutów funkcji jako miejsca przechowywania adnotacji. Załóżmy, że chcę pisać, w stylu C # (wskazując, że pewna metoda powinna być częścią interfejsu usługi sieci Web)

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

wtedy mogę zdefiniować

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

Następnie, gdy nadejdzie wywołanie usługi internetowej, sprawdzam metodę, sprawdzam, czy podstawowa funkcja ma atrybut is_webmethod (rzeczywista wartość jest nieistotna), i odmawiam usługi, jeśli metoda jest nieobecna lub nie ma być wywoływana przez Internet.

Martin v. Löwis
źródło
2
Czy uważasz, że ma to wady? np. Co się stanie, jeśli dwie biblioteki spróbują zapisać ten sam atrybut ad-hoc?
allyourcode
18
Myślałem o zrobieniu dokładnie tego. Potem się powstrzymałem. „Czy to zły pomysł?” Zastanawiałem się. Potem poszedłem do SO. Po kilku głupotach dookoła znalazłem to pytanie / odpowiedź. Nadal nie jestem pewien, czy to dobry pomysł.
allyourcode
7
Jest to zdecydowanie najbardziej uzasadnione użycie atrybutów funkcji spośród wszystkich odpowiedzi (stan na listopad 2012 r.). Większość (jeśli nie wszystkie) pozostałe odpowiedzi wykorzystują atrybuty funkcji jako zamiennik zmiennych globalnych; jednakże NIE pozbywają się stanu globalnego, co jest dokładnie problemem w przypadku zmiennych globalnych. Jest inaczej, ponieważ po ustawieniu wartość się nie zmienia; jest stały. Dobrą konsekwencją tego jest to, że nie napotykasz problemów synchronizacji, które są nieodłączne od zmiennych globalnych. Tak, możesz zapewnić własną synchronizację, ale o to chodzi: nie jest to automatycznie bezpieczne.
allyourcode
Rzeczywiście, mówię, dopóki atrybut nie zmienia zachowania omawianej funkcji, jest dobry. Porównaj z.__doc__
Dima Tisnek,
Tego podejścia można także użyć do dołączenia opisu wyjścia do dekorowanej funkcji, której brakuje w Pythonie 2. *.
Juh_
126

Użyłem ich jako zmiennych statycznych dla funkcji. Na przykład biorąc pod uwagę następujący kod C:

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

Mogę podobnie zaimplementować tę funkcję w Pythonie:

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

Zdecydowanie wpadłoby to w „nadużycie” na końcu spektrum.

mipadi
źródło
2
Ciekawy. Czy istnieją inne sposoby implementacji zmiennych statycznych w Pythonie?
Eli Bendersky,
4
-1, byłoby to zaimplementowane z generatorem w Pythonie.
124
To dość słaby powód, by głosować za tą odpowiedzią, która pokazuje analogię między C i Pythonem, nie opowiadając się za najlepszym możliwym sposobem napisania tej konkretnej funkcji.
Robert Rossney,
3
@RobertRossney Ale jeśli generatory są dobrym rozwiązaniem, oznacza to słabe wykorzystanie atrybutów funkcji. Jeśli tak, to jest to nadużycie. Nie jestem jednak pewien, czy głosować nadużycia, ponieważ pytanie również dotyczy tych: P
allyourcode
1
Zdecydowanie wydaje się to nadużyciem, według PEP 232, dzięki @ user140352 i komentarzowi chmielu i tej SO odpowiedzi
płyty
53

Możesz robić obiekty w JavaScript ... To nie ma sensu, ale działa;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
defnull
źródło
36
+1 Dobry przykład nadużycia tej funkcji, który jest jedną z rzeczy, o które pytano.
Michael Dunn
1
Czym różni się to od odpowiedzi mipadi? Wygląda na to samo, ale zamiast wartości int wartość atrybutu jest funkcją.
allyourcode
jest def test()naprawdę konieczne?
Keerthana Prabhakaran
15

Używam ich oszczędnie, ale mogą być dość wygodne:

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

Teraz mogę używać w logcałym module i przekierowywać dane wyjściowe po prostu przez ustawienie log.logfile. Istnieje wiele innych sposobów na osiągnięcie tego, ale ten jest lekki i prosty. I chociaż pachniało śmiesznie za pierwszym razem, kiedy to robiłem, doszedłem do wniosku, że pachnie lepiej niż posiadanie logfilezmiennej globalnej .

Robert Rossney
źródło
7
ponownie wąchać: To nie pozbywa się globalnego pliku dziennika. To po prostu wiewiórki w innej globalnej funkcji logowania.
allyourcode
2
@allyourcode: Ale może pomóc uniknąć konfliktów nazw, jeśli musisz mieć kilka globalnych plików dziennika dla różnych funkcji w tym samym module.
firegurafiku,
11

Atrybutów funkcji można używać do pisania lekkich zamknięć, które zawijają kod i powiązane dane:

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

Kevin Little
źródło
3
Po co wypychać funkcje, gdy mamy pod ręką zajęcia? I nie zapominajmy, że klasy mogą emulować funkcję.
muhuk,
1
też time.sleep(1)jest lepszy niżos.system('sleep 1')
Boris Gorelik
3
@bgbg Prawda, chociaż w tym przykładzie nie chodzi o spanie.
allyourcode
To zdecydowanie nadużycie; użycie tutaj funkcji jest całkowicie darmowe. muhuk ma rację: klasy są lepszym rozwiązaniem.
allyourcode
1
Chciałbym również zapytać: „Jaka jest to przewaga nad klasą?” aby przeciwdziałać niekorzystnej sytuacji, która nie jest tak oczywista dla wielu programistów Pythona.
cjs,
6

Stworzyłem ten dekorator pomocnika, aby łatwo ustawić atrybuty funkcji:

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

Przykładem zastosowania jest utworzenie zbioru fabryk i przeszukanie typu danych, które można utworzyć na poziomie meta funkcji.
Na przykład (bardzo głupi):

@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
DiogoNeves
źródło
Jak pomaga dekorator? Dlaczego nie ustawić factory1.datatype=listtuż pod deklaracją, jak dekoratorzy w starym stylu?
Ceasar Bautista
2
2 główne różnice: styl, wiele atrybutów jest łatwiejszych do ustawienia. Zdecydowanie można ustawić jako atrybut, ale moim zdaniem staje się on pełen wielu atrybutów, a także masz okazję rozszerzyć dekorator do dalszego przetwarzania (na przykład zdefiniowanie wartości domyślnych w jednym miejscu zamiast wszystkich miejsc, które używają funkcji lub wywołać dodatkową funkcję po ustawieniu atrybutów). Są inne sposoby na osiągnięcie wszystkich tych wyników, po prostu uważam, że jest to czystsze, ale chętnie zmieniam zdanie;)
DiogoNeves
Szybka aktualizacja: w Pythonie 3 musisz użyć items()zamiast iteritems().
Scott oznacza
4

Czasami używam atrybutu funkcji do buforowania już obliczonych wartości. Możesz także mieć ogólny dekorator, który uogólnia to podejście. Uważaj na problemy z współbieżnością i skutki uboczne takich funkcji!


źródło
Podoba mi się ten pomysł! Bardziej powszechną sztuczką do buforowania obliczonych wartości jest użycie dict jako domyślnej wartości atrybutu, którego wywołujący nigdy nie ma zamiar udostępnić - ponieważ Python ocenia to tylko raz podczas definiowania funkcji, możesz tam przechowywać dane i trzymać je na około. Chociaż używanie atrybutów funkcji może być mniej oczywiste, wydaje mi się, że jest mniej hackerskie.
Soren Bjornstad,
1

Zawsze zakładałem, że jedynym powodem, dla którego było to możliwe, było logiczne miejsce na umieszczenie dokumentu lub innych podobnych rzeczy. Wiem, że gdybym użył go w jakimkolwiek kodzie produkcyjnym, pomyliłby większość, którzy go czytają.

Dale Reidy
źródło
1
Zgadzam się z twoją główną uwagą na temat tego, że jest to prawdopodobnie mylące, ale odnosząc się do dokumentów: Tak, ale dlaczego funkcje mają atrybuty AD-HOC? Może istnieć stały zestaw atrybutów, jeden do przechowywania dokumentów.
allyourcode
@allyourcode Posiadanie ogólnej sprawy zamiast konkretnych spraw ad-hoc zaprojektowanych w języku upraszcza sprawę i zwiększa zgodność ze starszymi wersjami Pythona. (Na przykład kod, który ustawia / manipuluje ciągami znaków, będzie nadal działał z wersją Pythona, która nie wykonuje
ciągów znaków