Co robi functools.okłady?

W komentarzu do odpowiedzi na inne pytanie ktoś powiedział, że nie jest pewien, jakie functools.okłady robiły. Tak więc zadaję to pytanie, aby na StackOverflow był zapis tego w przyszłości: co to functools.okłady mają, dokładnie?

Author: Community, 2008-11-21

4 answers

Kiedy używasz dekoratora, zastępujesz jedną funkcję inną. Innymi słowy, jeśli masz dekoratora

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

Then when you say

@logged
def f(x):
   """does some math"""
   return x + x * x

To dokładnie to samo co powiedzenie

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

I twoja funkcja f zostanie zastąpiona funkcją with_logging. Niestety, oznacza to, że jeśli powiesz

print f.__name__

Wyświetli with_logging ponieważ to nazwa twojej nowej funkcji. W rzeczywistości, jeśli spojrzysz na docstring dla f, będzie pusty ponieważ with_logging nie ma docstringu, a więc napisanego przez Ciebie docstringu już tam nie będzie. Ponadto, jeśli spojrzysz na wynik pydoc dla tej funkcji, nie będzie ona wyświetlana jako przyjmująca jeden argument x; zamiast tego będzie wyświetlana jako przyjmująca *args i **kwargs, ponieważ to jest to, co zajmuje with_logging.

Jeśli użycie dekoratora zawsze oznaczało utratę informacji o funkcji, byłby to poważny problem. Dlatego mamy functools.wraps. Przyjmuje funkcję używaną w dekoratorze i dodaje funkcjonalność kopiowania nad nazwą funkcji, docstringiem, listą argumentów, itd. A ponieważ wraps jest dekoratorem, następujący kod robi poprawną rzecz:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print f.__name__  # prints 'f'
print f.__doc__   # prints 'does some math'
 777
Author: Eli Courtwright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-26 22:30:12

Bardzo często używam klas, zamiast funkcji, dla moich dekoratorów. Miałem pewne problemy z tym, ponieważ obiekt nie będzie miał wszystkich tych samych atrybutów, które są oczekiwane od funkcji. Na przykład obiekt nie będzie miał atrybutu __name__. Miałem z tym konkretny problem, który był dość trudny do wyśledzenia, gdzie Django zgłaszał błąd "obiekt nie ma atrybutu' __name__'". Niestety, dla dekoratorów klasowych, nie wierzę, że @wrap wykona tę robotę. Mam zamiast stworzył klasę base decorator Tak:

class DecBase(object):
    func = None

    def __init__(self, func):
        self.__func = func

    def __getattribute__(self, name):
        if name == "func":
            return super(DecBase, self).__getattribute__(name)

        return self.func.__getattribute__(name)

    def __setattr__(self, name, value):
        if name == "func":
            return super(DecBase, self).__setattr__(name, value)

        return self.func.__setattr__(name, value)

Ta klasa proxy wszystkie wywołania atrybutu do funkcji, która jest dekorowana. Możesz więc teraz utworzyć prosty dekorator, który sprawdza, czy podane są dwa argumenty w ten sposób:

class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)
 20
Author: Josh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-29 20:42:45
  1. Wymagane umiejętności: musisz wiedzieć, jak używać dekoratorów i specjalnie z okładami. Ten komentarz wyjaśnia to nieco jasno lub ten link również wyjaśnia to całkiem dobrze.

  2. Gdy używamy np: @wraps, po którym następuje nasza własna funkcja wrapper. Zgodnie ze szczegółami podanymi w tym linku, jest napisane, że

Functools.wraps jest wygodną funkcją do wywoływania update_wrapper () jako dekoratora funkcji, podczas definiowania wrappera funkcja.

Jest to równoważne częściowemu (update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated).

Więc @ wraps decorator faktycznie daje połączenie do functools.partial(func [, * args] [, * * keywords]).

Functools.definicja partial () mówi, że

Funkcja partial() jest używana do częściowej aplikacji funkcji, która "zawiesza" część argumentów funkcji i / lub słów kluczowych, co powoduje utworzenie nowego obiektu z uproszczonym podpisem. Na przykład, partial () może być użyty do utworzenia funkcji wywołującej, która zachowuje się jak funkcja int (), gdzie argument bazowy domyślnie wynosi dwa:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

Co prowadzi mnie do wniosku, że @wraps wywołuje partial () i przekazuje funkcję wrapper jako parametr do niej. Partial() na końcu zwraca uproszczoną wersję, tzn. obiekt tego, co znajduje się wewnątrz funkcji wrapper, a nie samą funkcję wrapper.

 1
Author: 3rdi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-10 03:14:01

W skrócie, functools.wraps jest tylko funkcją regularną. Rozważmy Ten oficjalny przykład . Za pomocą kodu źródłowego możemy zobaczyć więcej szczegółów na temat implementacji i kroków działania w następujący sposób:

  1. wraps (f) zwraca obiekt, powiedzmy O1 . Jest to obiekt class Partial
  2. następnym krokiem jest @o1... który jest notacją dekoratorską w Pythonie. Oznacza

Wrapper=O1.__ call _ _ (wrapper)

Sprawdzanie realizacji __call__, widzimy, że po tym kroku (lewa strona) wrapper staje się obiektem powstałym przez jaźń.func (*self.args, * args, * * newkeywords) sprawdzanie utworzenia O1 w _ _ new _ _ , znamy siebie.func jest funkcją update_wrapper . Używa parametru *args , prawej strony wrapper, jako jego 1st parametr. Sprawdzając ostatni krok update_wrapper , widać, że zwracany jest po prawej stronie wrapper, z niektórymi atrybutami modyfikowanymi w razie potrzeby.

 -1
Author: Yong Yang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-30 04:27:08