Разное

Functools wraps: Модуль functools на примерах

Модуль functools на примерах

Python содержит удобный модуль под названием functools. Функции внутри functools можно определить как функции высокого порядка, которые взаимодействуют и возвращают другие функции. В этой статье мы рассмотрим содержимое пакета functools:

  • lru_cache
  • partials
  • singledispatch
  • wraps

Давайте начнем с изучения создания простого кэша в Python.

Кэширование с functools.lru_cache

Модуль functools содержит весьма полезный декоратор под названием lru_cache. Обратите внимание на то, что он был добавлен в версии Python 3.2. Соответственно документации, этот декоратор «оборачивает функцию вызываемым запоминанием, которое сохраняет максимальное количество всех последних вызовов«. Другими словами, это декоратор, который добавляет кэширование к декорируемой функции. Давайте напишем быструю функцию, которая основана на примере из документации functools и охватывает кое-какие веб страницы. В нашем случае, мы охватим страницы из сайта документации Python.

# -*- coding: utf-8 -*-
import urllib.error
import urllib.request

from functools import lru_cache


@lru_cache(maxsize=24)
def get_webpage(module):
«»»
Поиск странице модуля
«»»
webpage = «https://docs.python.org/3/library/{}.html».format(module)

try:
with urllib.request.urlopen(webpage) as request:
return request.read()
except urllib.error.HTTPError:
return None


if __name__ == ‘__main__’:
modules = [‘functools’, ‘collections’, ‘os’, ‘sys’]
for module in modules:
page = get_webpage(module)

if page:
print(«{} страница модуля найдена!».format(module))


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

# -*- coding: utf-8 -*-

import urllib.error

import urllib.request

 

from functools import lru_cache

 

 

@lru_cache(maxsize=24)

def get_webpage(module):

    «»»

    Поиск странице модуля

    «»»

    webpage = «https://docs.python.org/3/library/{}.html».format(module)

    

    try:

        with urllib.request.urlopen(webpage) as request:

            return request.read()

    except urllib.error.HTTPError:

        return None

 

 

if __name__ == ‘__main__’:

    modules = [‘functools’, ‘collections’, ‘os’, ‘sys’]

    for module in modules:

        page = get_webpage(module)

        

        if page:

            print(«{} страница модуля найдена!».format(module))

В данном коде мы декорируем нашу функцию get_webpage при помощи lru_cache и указываем максимальный размер в 24 вызова. Далее мы устанавливаем переменную строки веб странице и передаем тот модуль, который нужно получить. На практике я обнаружил, что это работает лучше всего, если выполнить запуск в интерпретаторе Python, в таком как IDLE. Это позволит вам запустить цикл несколько раз над функцией Python. Когда вы запустите код, первое что вы заметите, что выдача выводится сравнительно медленно. Но если вы запустите его еще раз в той же сессии, вы увидите, что выдача появится мгновенно, что оговорит о том, что lru_cache кешировал вызовы корректно. Попробуйте сделать это лично в своем интерпретаторе, чтобы увидеть результат. Также существует типизированный параметр, который мы можем передать декоратору. Это Boolean, указывающий декоратору кешировать аргументы разных типов раздельно, если для типизации задано значение True.

functool.partial

Один из классов functools называется partial. Вы можете использовать для создания новой функции с частичным приложением аргументов и ключевых слов, которые вы передаете. Вы также можете использовать partial для «заморозки» части аргументов вашей функции и\или ключей, которые отображаются в новом объекте. Еще один способ применения partial это создание функции с разными настройками. Давайте взглянем на пример:

from functools import partial


def add(x, y):
return x + y

p_add = partial(add, 2)
p_add(4) # 6



from functools import partial

 

 

def add(x, y):

    return x + y

 

p_add = partial(add, 2)

p_add(4) # 6

Здесь мы создали простую функцию добавления, которая возвращает результат добавленных ею аргументов x и y. Далее мы создаем новую вызываемую, создав экземпляр partial и передав его нашей функции, а также аргумент для этой функции. Другими словами, мы в целом присваиваем параметру х в нашей функции значение 2. Наконец, мы вызываем p_add с аргументом числа 4, что в результате дает 6, так как 2+4=6.
Еще одним удобным вариантом использования для partials является передача аргументов коллбекам. Девайте взглянем на пример, используя wx:

# -*- coding: utf-8 -*-
import wx

from functools import partial


class MainFrame(wx.Frame):
«»»
Приложение показывает несколько кнопок
«»»

def __init__(self, *args, **kwargs):
«»»Конструктор»»»
super(MainFrame, self).__init__(parent=None, title=’Partial’)
panel = wx.Panel(self)

sizer = wx.BoxSizer(wx.VERTICAL)
btn_labels = [‘one’, ‘two’, ‘three’]
for label in btn_labels:
btn = wx.Button(panel, label=label)
btn.Bind(wx.EVT_BUTTON, partial(self.onButton, label=label))
sizer.Add(btn, 0, wx.ALL, 5)

panel.SetSizer(sizer)
self.Show()

def onButton(self, event, label):
«»»
Дожидаемся действия нажатия кнопки
«»»
print(‘Вы нажали: ‘ + str(label))


if __name__ == ‘__main__’:
app = wx.App(False)
frame = MainFrame()
app.MainLoop()


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

# -*- coding: utf-8 -*-

import wx

 

from functools import partial

 

 

class MainFrame(wx.Frame):

    «»»

    Приложение показывает несколько кнопок

    «»»

    

    def __init__(self, *args, **kwargs):

        «»»Конструктор»»»

        super(MainFrame, self).__init__(parent=None, title=’Partial’)

        panel = wx.Panel(self)

        

        sizer = wx.BoxSizer(wx.VERTICAL)

        btn_labels = [‘one’, ‘two’, ‘three’]

        for label in btn_labels:

            btn = wx.Button(panel, label=label)

            btn.Bind(wx.EVT_BUTTON, partial(self.onButton, label=label))

            sizer.Add(btn, 0, wx.ALL, 5)

        

        panel.SetSizer(sizer)

        self.Show()

 

    def onButton(self, event, label):

        «»»

        Дожидаемся действия нажатия кнопки

        «»»

        print(‘Вы нажали: ‘ + str(label))

 

 

if __name__ == ‘__main__’:

    app = wx.App(False)

    frame = MainFrame()

    app.MainLoop()

Здесь мы используем partial для вызова onButton, обработчика событий с дополнительным аргументом, который представлен в виде ярлыка кнопки. Это может выглядеть не слишком полезным, но если вы связанны с программированием графического интерфейса, вы заметите, как часто люди задаются вопросом «а как это сделать?». Конечно, вы также можете использовать lambda функцию вместо передачи аргументов коллбекам.

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!


Один из способов применения используется в работе для автоматического теста Фреймворка. Мы тестируем пользовательский интерфейс при помощи Python и нам нужно быть в состоянии передать функцию, чтобы отменить определенные диалоги. Обычно вы можете передать функцию вместе с названием диалога для отмены, но для корректной работы его потребуется вызвать в конкретной точке в процессе. Так как я не могу показать тот код, вот очень простой пример передачи функции partial:

from functools import partial


def add(x, y):
return x + y


def multiply(x, y):
return x * y


def run(func):
print(func())


def main():
a1 = partial(add, 1, 2)
m1 = partial(multiply, 5, 8)
run(a1)
run(m1)

if __name__ == «__main__»:
main()


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

from functools import partial

 

 

def add(x, y):

    return x + y

 

 

def multiply(x, y):

    return x * y

 

 

def run(func):

    print(func())

 

 

def main():

    a1 = partial(add, 1, 2)

    m1 = partial(multiply, 5, 8)

    run(a1)

    run(m1)

 

if __name__ == «__main__»:

    main()

Здесь мы создаем несколько функций partial в нашей главной функции. Далее мы передаем их нашей функции run, вызываем её и затем выводим результат вызванной функции.

Перегрузка функции с functools.singledispatch

Совсем недавно в Python была добавлена поддержка partial для перегрузки функции в версии 3.4. Этот инструмент является аккуратным небольшим декоратором для модуля functools под названием singledispatch. Этот декоратор превращает вашу обычную функцию в функцию родовой рассылки. Однако обратите внимание на то, что singledispatch появляется только на основании типа первого аргумента. Давайте взглянем на пример, чтобы увидеть, как это работает.

from functools import singledispatch


@singledispatch
def add(a, b):
raise NotImplementedError(‘Unsupported type’)


@add.register(int)
def _(a, b):
print(«First argument is of type «, type(a))
print(a + b)


@add.register(str)
def _(a, b):
print(«First argument is of type «, type(a))
print(a + b)


@add.register(list)
def _(a, b):
print(«First argument is of type «, type(a))
print(a + b)

if __name__ == ‘__main__’:
add(1, 2)
add(‘Python’, ‘Programming’)
add([1, 2, 3], [5, 6, 7])


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

from functools import singledispatch

 

 

@singledispatch

def add(a, b):

    raise NotImplementedError(‘Unsupported type’)

 

 

@add.register(int)

def _(a, b):

    print(«First argument is of type «, type(a))

    print(a + b)

 

 

@add.register(str)

def _(a, b):

    print(«First argument is of type «, type(a))

    print(a + b)

 

 

@add.register(list)

def _(a, b):

    print(«First argument is of type «, type(a))

    print(a + b)

 

if __name__ == ‘__main__’:

    add(1, 2)

    add(‘Python’, ‘Programming’)

    add([1, 2, 3], [5, 6, 7])

Здесь мы импортировали singledispatch из functools и применили его в простой функцию, которую мы назвали add. Эта функция является всеохватывающей и может быть вызвана только в том случае, если никакие другие декорированные функции не обрабатывают переданный тип. Вы заметите, что мы обрабатываем целые числа, строки и списки как первый аргумент. Если мы вызовем нашу функцию add с чем-то еще, например, со словарем, то это приведет к ошибке NotImplementedError. Попробуйте запустить этот код самостоятельно. Вы увидите выдачу, которая выглядит примерно следующим образом:

First argument is of type <class ‘int’>
3

First argument is of type <class ‘str’>
PythonProgramming

First argument is of type <class ‘list’>
[1, 2, 3, 5, 6, 7]

Traceback (most recent call last):
File «overloads.py», line 30, in <module>
add({}, 1)
File «/usr/local/lib/python3.5/functools.py», line 743, in wrapper
return dispatch(args[0].__class__)(*args, **kw)
File «overloads.py», line 5, in add
raise NotImplementedError(‘Unsupported type’)
NotImplementedError: Unsupported type


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

First argument is of type <class ‘int’>

3

 

First argument is of type <class ‘str’>

PythonProgramming

 

First argument is of type <class ‘list’>

[1, 2, 3, 5, 6, 7]

 

Traceback (most recent call last):

    File «overloads.py», line 30, in <module>

        add({}, 1)

    File «/usr/local/lib/python3.5/functools.py», line 743, in wrapper

        return dispatch(args[0].__class__)(*args, **kw)

    File «overloads.py», line 5, in add

        raise NotImplementedError(‘Unsupported type’)

NotImplementedError: Unsupported type

Как вы видите, код работает именно так, как и ожидалось. Он вызывает подходящую функцию, основанную на первом типе аргумента. Если тип не обработан, то это приведет к ошибке NotImplementedError. Если вы хотите узнать, какие типы мы в данный момент обрабатываем, вы можете добавить следующую часть кода в конце файла, желательно перед строкой, которая вызывает ошибку:

print(add.registry.keys())



print(add.registry.keys())

Это выведет что-то на подобии этого:

dict_keys([<class ‘str’>, <class ‘int’>, <class ‘list’>, <class ‘object’>])



dict_keys([<class ‘str’>, <class ‘int’>, <class ‘list’>, <class ‘object’>])

Это говорит нам о том, что мы можем обрабатывать строки, целые числа, списки и объекты (по умолчанию). Декоратор singledispatch также поддерживает укладку. Это позволяет нам создавать перегруженную функцию, которая обрабатывается несколько раз. Давайте взглянем на пример:

from functools import singledispatch
from decimal import Decimal


@singledispatch
def add(a, b):
raise NotImplementedError(‘Unsupported type’)


@add.register(float)
@add.register(Decimal)
def _(a, b):
print(«First argument is of type «, type(a))
print(a + b)


if __name__ == ‘__main__’:
add(1.23, 5.5)
add(Decimal(100.5), Decimal(10.789))


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

from functools import singledispatch

from decimal import Decimal

 

 

@singledispatch

def add(a, b):

    raise NotImplementedError(‘Unsupported type’)

 

 

@add.register(float)

@add.register(Decimal)

def _(a, b):

    print(«First argument is of type «, type(a))

    print(a + b)

 

 

if __name__ == ‘__main__’:

    add(1.23, 5.5)

    add(Decimal(100.5), Decimal(10.789))

Это, как правило, говорит Python о том, что одна из перегруженных функции add может обрабатывать типы float и decimal.Decimal в качестве первого аргумента. Если вы запустите этот код, вы увидите следующее:

First argument is of type <class ‘float’>
6.73

First argument is of type <class ‘decimal.Decimal’>
111.2889999999999997015720510
dict_keys([<class ‘float’>, <class ‘int’>, <class ‘object’>, <class ‘decimal.Dec\
imal’>



First argument is of type <class ‘float’>

6.73

 

First argument is of type <class ‘decimal.Decimal’>

111.2889999999999997015720510

dict_keys([<class ‘float’>, <class ‘int’>, <class ‘object’>, <class ‘decimal.Dec\

imal’>

Возможно, вы уже заметили, но из-за того, как эти функции были написаны, вы можете складывать декораторы для обработки всех случаев, как в предыдущем примере, так и в этом, в перегруженную функцию. В любом случае, в нормальном случае перегрузки, каждое форсирование вызывает другой код, вместо того, чтобы выполнять одну и ту же задачу.

functools.wraps

Мы рассмотрим не самый известный инструмент, под названием called, который также является частью модуля functools. Вы можете использовать его как декоратор для исправления docstrings и наименований декорированных функций.

Имеет ли это значение? Как минимум, звучит немного странно, но если вы пишете API, или любой другой код, который будет использоваться кем-нибудь другим, а не только вами, эта часть может быть весьма важной. Причина в том, что когда вы используете интроспекцию Python, что бы разобраться в чужом коде, декорированная функция выдаст неправильную информацию. Давайте взглянем на простой пример, который я продублировал из decorum.py:

def another_function(func):
«»»
Функция которая принимает другую функцию
«»»
def wrapper():
«»»
Оберточная функция
«»»
val = «The result of %s is %s» % (func(),
eval(func())
)

return val
return wrapper


@another_function
def a_function():
«»»Обычная функция»»»
return «1+1″


if __name__ == «__main__»:
print(a_function.__name__)
print(a_function.__doc__)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

def another_function(func):

    «»»

    Функция которая принимает другую функцию

    «»»

    def wrapper():

        «»»

        Оберточная функция

        «»»

        val = «The result of %s is %s» % (func(),

                eval(func())

                )

        

        return val

    return wrapper

 

 

@another_function

def a_function():

    «»»Обычная функция»»»

    return «1+1»

 

 

if __name__ == «__main__»:

    print(a_function.__name__)

    print(a_function.__doc__)

В этом коде мы декорируем функцию под названием a_function с another_function. Вы можете проверить название функции и docstring, выведя их, используя свойства функции __name__ и __doc__ . Если вы запустите данный пример, вы получите следующую выдачу:

wrapper

Оберточная функция



wrapper

 

Оберточная функция

Это не правильно! Если вы запустите эту программу IDLE или в интерпретаторе, станет понятно, насколько это может запутать.

import decorum
help(decorum)

Help on module decorum:

NAME
decorum -

FILE
/home/mike/decorum.py

FUNCTIONS
a_function = wrapper()
Оберточная функция

another_function(func)
Функция принимает другую функцию


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import decorum

help(decorum)

 

Help on module decorum:

 

NAME

    decorum —

 

FILE

    /home/mike/decorum.py

 

FUNCTIONS

    a_function = wrapper()

        Оберточная функция

 

    another_function(func)

        Функция принимает другую функцию

wrapper() # Оберточная функция



wrapper() # Оберточная функция

В целом, здесь происходит следующее: декоратор меняет название декорированной функции и docstring на свое собственное.

Спасение во wraps!

Как исправить этот бардак? Разработчики Python предоставили нам отличное решение в лице functools.wraps! Давайте посмотрим:

from functools import wraps


def another_function(func):
«»»
Функция которая принимает другую функцию
«»»

@wraps(func)
def wrapper():
«»»
Оберточная функция
«»»
val = «The result of %s is %s» % (func(),eval(func()))
return val
return wrapper


@another_function
def a_function():
«»»Обычная функция»»»
return «1+1″


if __name__ == «__main__»:
#a_function()
print(a_function.__name__)
print(a_function.__doc__)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

from functools import wraps

 

 

def another_function(func):

    «»»

    Функция которая принимает другую функцию

    «»»

    

    @wraps(func)

    def wrapper():

        «»»

        Оберточная функция

        «»»

        val = «The result of %s is %s» % (func(),eval(func()))

        return val

    return wrapper

 

 

@another_function

def a_function():

    «»»Обычная функция»»»

    return «1+1»

 

 

if __name__ == «__main__»:

    #a_function()

    print(a_function.__name__)

    print(a_function.__doc__)

Здесь мы импортируем wraps из модуля functools и используем его в качестве декоратора для вложенной функции-обертки внутри another_function. Если вы запустите его сейчас, выдача изменится:

a_function
Обычная функция



a_function

Обычная функция

Теперь все названия прописаны правильно. Если вы перейдете в интерпретатор Python, функция help также будет работать корректно. Я пропущу копирование выдачи здесь, и хочу, чтобы вы попробовали лично сделать это.

Подведем итоги

Давайте подумаем. В этой статье вы научились базовому кэшированию с использованием lru_cache. После этого мы изучили partial, который позволяет нам «замораживать» часть аргументов и\или ключей в вашей функции, позволяя вам создавать новый объект, который вам нужно вызвать. Далее, мы использовали singledispatch для перегрузки функций в Python. Так как это только позволяет функции перегрузиться на основании первого аргумента, этот инструмент может оказаться весьма кстати в будущем! Наконец, мы рассмотрели wraps, который обладает весьма узкой спецификой: исправление docstring и названий функций, которые были декорированы таким образом, что у них нет docstring декоратора, или названия.

Использование functool.wraps в декораторах Python

Давайте предположим, что у нас есть простой декоратор, «mydeco», который берет вывод функции и помещает его в строку, за которой следуют три восклицательных знака:

def mydeco(func):
    def wrapper(*args, **kwargs):
        return f'{func(args, **kwargs)}!!!'
    return wrapper

Давайте теперь используем наш декоратор для двух разных функций:

@mydeco
def add(a, b):
    '''Add two objects together, the long way'''
    return a + b
@mydeco
def mysum(*args):
    '''Sum any numbers together, the long way'''
    total = 0
    for one_item in args:
        total += one_item
    return total

Что происходит, когда я запускаю эти функции? Они работают как мы ожидали:

>>> add(10, 20)
'30!!!'
>>> mysum(10, 20, 30, 40, 50)
'150!!!

Фантастика! Мы возвращаем результат каждой функции в виде строки с восклицательными знаками. Декоратор работает.

Но есть несколько проблем с тем, что мы сделали. Например, что если я запрашиваю каждую функцию о ее имени:

>>> add.__name__
'wrapper'
>>> mysum.__name__
'wrapper'

Атрибут __name__, который возвращает нам имя функции при ее определении, теперь отражает возвращенную внутреннюю функцию «wrapper» используемую в нашем декораторе. Это так и есть, но это не то что нам нужно.

Может быть еще хуже, если мы попросим вернуть строку документации:

>>> help(add)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
>>> help(mysum)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)

Другими словами: теперь мы получаем строку документации и функцию «wrapper», внутренней функции. И это проблема, потому что теперь мы не может получить основные атрибуты декорированной функции.

Мы можем решить эту проблему, хотя бы частично, назначив атрибуты __name__ и __doc__ в нашем декораторе:

def mydeco(func):
    def wrapper(*args, **kwargs):
        return f'{func(args, **kwargs)}!!!'
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper

Если мы используем эту версию декоратора, то каждый раз, когда мы возвращаем «wrapper» от нашего декоратора, мы вручную присваиваем имя исходной функции и строку документации для нее.

>>> help(add)
Help on function add in module __main__:

add(*args, **kwargs)
     Add two objects together, the long way
>>> help(mysum)
Help on function mysum in module __main__:

mysum(*args, **kwargs)
    Sum any numbers together, the long way

Хорошая новость заключается в том, что мы исправили проблему имен и строк документации. Но сигнатура функции все еще остается общей *args и **kwargs.

Решение заключается в использовании functools.wraps. Она предназначен для решения именно этих проблем. Ирония, конечно, заключается в том, что это может доставить больше проблем, чем обычно это делают декораторы, потому что functools.wraps — это … декоратор, который принимает аргумент! Вот как это выглядит:

from functools import wraps

def mydeco(func):
    @wraps(func)
    def wrapper(*args, *kwargs):
        return f'{func(args, **kwargs)}!!!'
    return wrapper

Обратите внимание на то, что мы сделали здесь: мы использовали декоратор «functool.wraps», чтобы декорировать нашу внутреннюю функцию, функцию «wrapper». Применяя этот декоратор «wraps» к нашей внутренней функции, мы копируем имя, строку документации и сигнатуру функции в нашу внутреннюю функцию, избегая проблем, с которыми мы сталкивались ранее:

>>> help(add)
Help on function add in module main:
add(a, b)
     Add two objects together, the long way

>>> help(mysum)
Help on function mysum in module main:
mysum(*args)
     Sum any numbers together, the long way

Ее использование почти ничего не стоит (т. е. одна строка кода) и она делает вашу декорированную функцию более естественной.

Оригинальная статья: Making your Python decorators even better, with functool.wraps

Была ли вам полезна эта статья?

[3 / 3.7]

Введение в Python Functools / Блог компании OTUS. Онлайн-образование / Хабр

Привет, хабровчане. Мы подготовили перевод еще одного полезного материала в преддверии старта курса «Разработчик Python».


Python – высокоуровневый объектно-ориентированный язык программирования. Одно из самых больших преимуществ Python заключается в том, что в нем есть специальный функционал, который позволяет писать переиспользуемый код с помощью встроенных инструментов языка.

Functools – это библиотека Python, которая предназначена для работы с функциями высшего порядка. Такие функции могут принимать в себя другие функции и возвращать функции. Они помогают разработчиком писать код, который можно переиспользовать. Функции можно использовать или расширять, не переписывая их полностью. Модуль functools в Python предоставляет различные инструменты, которые позволяют добиться описанного эффекта. Например, следующие:

  • Partial;
  • Полное упорядочивание;
  • update_wrapper для partial.

Функция partial является одним из основных инструментов, предоставляемых functools. Давайте разберемся с ней на примерах.

Функция partial в Python

В модуле functools одним из важнейших инструментов считается функция partial. С помощью partial функции можно заменить существующую функцию, которой уже переданы аргументы. Более того, мы также можем создать новую версию функции, добавив качественную документацию.

Мы можем создавать новые функции, передавая частичные аргументы. Также мы можем заморозить некоторые аргументы функции, что приведет к появлению нового объекта. Еще один способ представить partial, заключается в том, что с ее помощью мы можем создать функцию со значениями по умолчанию. Partial поддерживает ключевые слова и позиционные аргументы в качестве фиксированных.

Давайте разберемся на примерах.

Как создать функцию partial?

Чтобы создать partial-функцию, используйте partial() из библиотеки functools. Пишется она следующим образом:

partial(func, /, *args, ** kwargs)

Так вы создадите partial функцию, которая вызовет func, передав ей фиксированные ключевые слова и позиционные аргументы. Здесь обычно передаются несколько необходимых аргументов для вызова функции func. Остальные аргументы передаются в *args и **kwargs.

Допустим, функция ниже складывает два числа:

def multiply(x, y):
 
    return x * y

Теперь рассмотрим случай, когда нам понадобилось удвоить или утроить заданное число. В таком случае новые функции мы определим, как показано ниже:

def multiply(x, y):
        return x * y
 
def doubleNum(x):
       return multiply(x, 2)
 
def tripleNum(x):
       return multiply(x, 3)

Когда сценария работы функции всего 2-3, конечно, логичнее сделать, как показано выше. Но когда нужно написать еще 100 таких функций, то смысла переписывать один и тот же код столько раз нет. Здесь нам и пригодятся partial функции. Чтобы ими воспользоваться, во-первых, нам нужно импортировать partial из Functools.

from functools import partial
 
def multiply(x, y):
       return x * y
 
doubleNum = partial(multiply, 2)
   tripleNum = partial(multiply, 3)
 
Print(doubleNum(10))
 
Output: 20

Как видно из примера, значения по умолчанию будут заменены переменными слева. Вместо x будет 2, а вместо y будет 10 при вызове doubleNum(10). В этом примере порядок не будет иметь принципиального значения, но в других вариантах использования он может иметь значение. Давайте рассмотрим пример на этот случай, чтобы понять порядок замены переменных.

from functools import partial
def orderFunc(a,b,c,d):
      return a*4 + b*3 + c*2 + d
 
result = partial(orderFunc,5,6,7)
print(result(8))
 
Output: 60

Полное упорядочивание

У нас появилась функция orderFunc(), в которой происходит умножение a на 4, b на 3, c на 2 и добавление d к сумме значений.

Мы создали partial функцию result(), которая вызывает orderFunc() со значениями 5, 6 и 7. Теперь значения 5, 6 и 7 будут заменять переменные a, b и c соответственно. На место переменной d встанет 8, так как она передается при вызове result(). В результате получится (4*5 + 6*3 + 7*2 + 8) = 60.

В этом случае порядок передаваемых значений будет иметь значение, поскольку если изменится порядок, то изменится и результат. Чтобы зафиксировать переменные, можно использовать ключевые слова вместо позиционных аргументов. Давайте перепишем код приведенный выше используя ключевые слова в качестве аргументов.

from functools import partial
def orderFunc(a,b,c,d):
       return a*4 + b*3 + c*2 + d
 
 result = partial(orderFunc,c=5,d=6)
print(result(8,4))
 
Output: 60

Здесь мы зафиксировали значение 5 за переменной c и 6 за переменной d. Вместо переменных a и b встанут значения 8 и 4. В результате получится (8*4+4*3+5*2+6) = 60.

Partial функцию можно определить в цикле и использовать для повторяющихся вычислений. Давайте рассмотрим пример:

from functools import partial
 
def add(x,y):
      return x + y
 
add_partials = []
for i in range (1, 10):
      function = partial(add, i)
      add_partials.append(function)
      print('Sum of {} and 2 is {}'.format(i,add_partials[i-1](2)))
 
  
Output:
 
Sum of 1 and 2 is 3
Sum of 2 and 2 is 4
Sum of 3 and 2 is 5
Sum of 4 and 2 is 6
Sum of 5 and 2 is 7
Sum of 6 and 2 is 8
Sum of 7 and 2 is 9
Sum of 8 and 2 is 10
Sum of 9 and 2 is 11

В этом примере мы будем суммировать определенный диапазон значений с 2, переиспользуя имеющуюся функцию. Мы можем вызвать partial в цикле и использовать ее функционал для вычисления сумм. Как видно из значений на выходе, у нас есть цикл от 1 до 10 и все значения в этом промежутке прибавляются к 2 с помощью функции partial, которая вызывает функцию сложения.

Метаданные

Несмотря на то, что partial функции являются независимыми, они хранят память (метаданные) функции, которую они расширяют.

from functools import partial
 
def add(x,y):
      return x + y
 
# create a new function that multiplies by 2
result = partial(add,y=5)
print(result.func)
print(result.keywords)
 
Output:
<function add at 0x7f27b1aab620>
{'y': 5}

Первый вызов func передаст имя функции и ее адрес в памяти, а второй вызов с keywords передаст ключевые слова в функцию. Таким образом функции partial можно назвать самодокументирующимися с помощью метаданных, которые они получают от расширяемой функции.

update_wrapper для partial

Мы можем обновлять метаданные функции с помощью другого инструмента из functools. Update_wrapper – это инструмент, который можно использовать для обновления метаданных функции. Давайте разберемся с ним на примере.

def multiply(x, y):
 
    """Test string."""
 
    return x * y
 
 result = functools.partial(multiply, y=2)
 
 try:
 
    print ('Function Name:'+result.__name__)
 
except AttributeError:
 
    print('Function Name: __no name__')
 
 print ('Function Doc:'+result.__doc__)
 
 print('Updating wrapper:')
 
functools.update_wrapper(result, multiply)
 
 print ('Function Name:'+result.__name__)
 
print ('Function Doc:'+result.__doc__)
 
Output:
 
Function Name: __no name__
 
Function Doc:partial(func, *args, **keywords) - new function with partial application
 
    of the given arguments and keywords.
 
 
Updating wrapper:
 
Function Name: multiply
Function Doc: Test string.

Теперь как видно из выходных данных, до использования обертки (wrapper) у функции не было закрепленного за ней имени или документа. Как только мы обновили name и doc функции с помощью update_wrapper, в выводе увидели соответствующий результат.

Заключение

С помощью functools мы можем избавиться от избыточного кода и увеличить возможности переиспользования кода в Python. Чем чаще вы будете использовать функцию partial, тем больше вариантов использования будете открывать. Экспериментируйте и получайте от этого удовольствие!


Успеть на курс.


Что делает functools.wraps? — python

Комментируя этот ответ на другой вопрос, кто-то сказал, что они не были уверены, что делает functools.wraps . Итак, я задаю этот вопрос, чтобы в будущем на StackOverflow была сделана запись об этом: что именно делает functools.wraps ?

python

decorator

functools

Поделиться

Источник


Eli Courtwright    

21 ноября 2008 в 14:53

6 Ответов




889

Когда вы используете декоратор, вы заменяете одну функцию на другую. Другими словами, если у вас есть декоратор

def logged(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

это в точности то же самое, что сказать

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

и ваша функция f заменяется функцией with_logging. К сожалению, это означает, что если вы потом скажете

print(f.__name__)

он будет печатать with_logging , потому что это имя вашей новой функции. На самом деле , если вы посмотрите на строку docstring для f, она будет пустой, потому что with_logging не имеет строки docstring, и поэтому строка docstring, которую вы написали, больше не будет там. Кроме того, если вы посмотрите на результат pydoc для этой функции, он не будет указан как принимающий один аргумент x ; вместо этого он будет указан как принимающий *args и **kwargs , потому что это то, что принимает with_logging.

Если бы использование декоратора всегда означало потерю этой информации о функции, это было бы серьезной проблемой. Вот почему у нас есть functools.wraps . Это берет функцию, используемую в декораторе, и добавляет функциональность копирования по имени функции, docstring, списку аргументов и т. д. А поскольку wraps сам является декоратором, следующий код делает правильную вещь:

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'

Поделиться


Eli Courtwright    

21 ноября 2008 в 14:53



20

Я очень часто использую классы, а не функции, для моих декораторов. У меня были некоторые проблемы с этим, потому что объект не будет иметь все те же атрибуты, которые ожидаются от функции. Например, объект не будет иметь атрибута __name__ . У меня была конкретная проблема с этим, что было довольно трудно trace, где Django сообщал об ошибке «объект не имеет атрибута’ __name__ ‘». К сожалению, для декораторов в стиле класса я не верю, что @wrap справится с этой работой. Вместо этого я создал базовый класс декоратора вот так:

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)

Этот класс проксирует все вызовы атрибутов к оформляемой функции. Таким образом, теперь вы можете создать простой декоратор, который проверяет, что 2 аргумента указаны следующим образом:

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

        return self.func(*args)

Поделиться


Josh    

03 декабря 2009 в 23:46



3

это исходный код обертывания:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

   Returns a decorator that invokes update_wrapper() with the decorated
   function as the wrapper argument and the arguments to wraps() as the
   remaining arguments. Default arguments are as for update_wrapper().
   This is a convenience function to simplify applying partial() to
   update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

Поделиться


Baliang    

14 декабря 2018 в 06:01




3

По состоянию на python 3.5+:

@functools.wraps(f)
def g():
    pass

Это псевдоним для g = functools.update_wrapper(g, f) . Он делает ровно три вещи:

  • он копирует атрибуты __module__ , __name__ , __qualname__ , __doc__ и __annotations__ из f на g . Этот список по умолчанию находится в WRAPPER_ASSIGNMENTS, вы можете увидеть его в источнике functools .
  • он обновляет __dict__ из g со всеми элементами из f.__dict__ . (см. WRAPPER_UPDATES в источнике)
  • он устанавливает новый атрибут __wrapped__=f на g

Следствием этого является то, что g появляется как имеющий то же самое имя, docstring, имя модуля и подпись, чем f . Единственная проблема заключается в том, что в отношении сигнатуры это не совсем так: просто inspect.signature по умолчанию следует за цепочками обертки. Вы можете проверить это, используя inspect.signature(g, follow_wrapped=False) , как описано в документе doc . Это имеет неприятные последствия:

  • код оболочки будет выполняться даже в том случае, если указанные аргументы являются недопустимыми.
  • код-оболочка не может легко получить доступ к аргументу, используя его имя, из полученного *args, **kwargs. действительно нужно было бы обрабатывать все случаи (позиционные, ключевые, по умолчанию) и поэтому использовать что-то вроде Signature.bind() .

Теперь есть небольшая путаница между functools.wraps и декораторами, потому что очень частый случай использования для разработки декораторов-это обернуть функции. Но и то и другое-совершенно независимые понятия. Если вам интересно понять разницу, я реализовал вспомогательные библиотеки для обоих: decopatch , чтобы легко писать декораторы, и makefun , чтобы обеспечить замену @wraps для сохранения подписи . Обратите внимание, что makefun опирается на тот же проверенный трюк, что и знаменитая библиотека decorator .

Поделиться


smarie    

11 марта 2019 в 13:16



1

  1. Обязательное условие: вы должны знать, как использовать декораторы и особенно с обертками. Этот комментарий объясняет это немного ясно, или эта ссылка также объясняет это довольно хорошо.

  2. Всякий раз, когда мы используем, например: @wraps, за которым следует наша собственная функция обертки. В соответствии с деталями, приведенными в этой ссылке , он говорит, что

functools.wraps-это функция удобства для вызова update_wrapper() в качестве декоратора функций при определении функции-оболочки.

Это эквивалентно частичной(update_wrapper, обернутый=укутав, присвоенный=назначенных обновленными=обновлено).

Так что @wraps decorator фактически дает вызов functools.частичный(func[,*args][, **keywords]).

Определение functools.partial() говорит, что

partial() используется для частичного применения функции, которая “freezes” некоторая часть аргументов функции и / или ключевых слов приводит к новому объекту с упрощенной сигнатурой. Например, partial() можно использовать для создания вызываемого объекта, который ведет себя как функция int(), где базовый аргумент по умолчанию равен двум:

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

Что приводит меня к выводу, что @wraps вызывает partial() и передает ему вашу функцию-оболочку в качестве параметра. partial() в конце концов возвращает упрощенную версию, т. е. объект того, что находится внутри функции-оболочки, а не саму функцию-оболочку.

Поделиться


3rdi    

10 марта 2018 в 03:14



-4

Короче говоря, functools.wraps — это просто обычная функция. Давайте рассмотрим этот официальный пример . С помощью исходного кода мы можем увидеть более подробную информацию о реализации и выполняемых шагах следующим образом:

  1. wraps (f) возвращает объект, скажем O1 . Это объект класса Partial
  2. Следующий шаг-это @O1… , который является обозначением декоратора в python. Это значит:

фантик=O1.__call__(wrapper)

Проверяя реализацию _ _ call__ , мы видим, что после этого шага (левая сторона ) оболочка становится объектом, полученным self.func(*self.args, *args, **newkeywords) проверяя создание O1 в _ _ new__, мы знаем, что self.func -это функция update_wrapper . Он использует параметр *args, правую обертку, в качестве своего 1-го параметра. Проверяя последний шаг update_wrapper, можно увидеть, что возвращается правая обертка с некоторыми атрибутами, измененными по мере необходимости.

Поделиться


Yong Yang    

30 марта 2018 в 04:27



Похожие вопросы:

Как изменить параметры, хранящиеся в functools.wraps?

У меня есть декоратор, который проверяет некоторые параметры и передает проверенный ключ для различных функций: from functools import wraps ref validate(f): @wraps(f) # This is to ensure docstrings…

Как реализовать эквивалент Python functools.wraps?

Я знаю, что могу обернуть функции в Go через возвращаемую функцию, но как реализовать эквивалент Python functools.wraps в Go? Как прикрепить атрибут к функциям в Go? Как код ниже в Python. from…

модуль оформителя против functools.wraps

Функциональные возможности модуля decorator и functools.wraps тесно связаны. Каковы различия между ними (по состоянию на Python 3.3 / 3.4)? Я знаю одно отличие: wraps 9000>3+ лет назад decorator…

Python шаблон декоратора: сокращение дублирования кода с участием внутренних функций и functools.wraps

Я вижу много документации на StackOverflow и в других местах о том, как писать Python декораторов. Они обычно рекомендуют использовать functools.wraps и (потенциально несколько) внутренних функций….

Функция, оформленная с помощью functools.wraps поднимает TypeError с именем обертки. Почему? Как этого избежать?

def decorated(f): @functools.wraps(f) def wrapper(): return f() return wrapper @decorated def g(): pass functools.wraps выполняет свою работу по сохранению имени g : >>> g.__name__ ‘g’ Но…

Что делает `cache = obj.cache = {} ‘ в этом декораторе?

Следующий декоратор запоминает переданную ему функцию. Я не понимаю, что делает первая линия этого декоратора. Как он может присвоить значение obj.cache , если нет никакой гарантии, что оно…

functools.wraps для python 2.4

functools.wraps не доступен в python 2.4.Is есть какой-либо другой модуль, который может быть использован вместо этого в python 2.4?

Использование functools.wraps с декоратором журнала

Я пытаюсь написать простой декоратор, который регистрирует данный оператор перед вызовом украшенной функции. Зарегистрированные операторы должны оба казаться исходящими из одной и той же функции,…

в чем разница между functools.wraps и update_wrapper

Я не могу найти, в чем разница между этими двумя функциями python. functools.wraps и update_wrapper Может кто-нибудь дать мне пример кода, чтобы я мог понять, в чем разница

functools.wraps эквивалент для декоратора класса

Когда мы украшаем функцию, мы используем functools.wraps , чтобы сделать украшенную функцию похожей на оригинал. Есть ли Ват, чтобы сделать то же самое, когда мы хотим украсить класс? def…

Про Python — Справочник — functools.partial

Для указанной функции возвращает объект, поддерживающий вызов, частично применяющий к функции указанные аргументы.

Частичное (partial) применение некоторых аргументов, позволяет уменьшить арность функции (количество аргументов).
В итоге, на выходе мы получим объект с упрощённой сигнатурой.

    from functools import partial

def count_animals(number, kind, adjective='big'):
print('%s %s %s' % (number, adjective, kind))

count_cats = partial(count_animals, kind='cats')
count_cats() # TypeError: count_animals() takes exactly 2 arguments (1 given)
count_cats(12) # 12 big cats
count_cats(13, kind='kittens') # 13 big kittens
count_cats.keywords # {'kind': 'cats'}

ten_huge_cats = partial(count_cats, 10, adjective='huge')
ten_huge_cats() # 10 huge cats

Возвращаемый функцией partial объект весьма похож на функцию: поддерживает вызов, слабые ссылки на себя и может иметь атрибуты.

Однако, есть и важные отличия:

  • для него не создаются автоматически атрибуты __name__ и __doc__;
  • объявленные в рамках классов partial-объекты ведут себя как статические методы, т.е. не преобразуются в связанные методы в процессе разрешения атрибутов класса.

Если при вызове partial-объекта используются позиционные аргументы, то они добавляются к перечисленным в args.
Если при вызове partial-объекта используются именованные аргументы, то они дополняют, либо заменяют перечисленные в kwargs.

Отличие от каррирования: объект с частично применёнными аргументами возвращает результат функции, а не функцию в цепочке каррирования.

Атрибуты возвращаемого partial-объекта

funcФункция, к которой будет перенаправлен вызов с применением аргументов.
argsПозиционные аргументы, которые будут переданы в вызываемую функцию при вызове объекта.
keywordsИменованные аргументы, которые будут переданы в вызываемую функцию при вызове объекта.

functools — Инструменты для управления функциями — PyMOTW 3

Модуль functools предоставляет инструменты для адаптации или расширения
функции и другие вызываемые объекты без полного переписывания
их.

Декораторы

Основной инструмент, предоставляемый модулем functools , — это класс
частичный , который можно использовать для «обертывания» вызываемого объекта с помощью
аргументы по умолчанию. Результирующий объект сам по себе вызывается и может быть
рассматривается как исходная функция.Требуется все
те же аргументы, что и исходный, и могут быть вызваны с дополнительными
также позиционные или именованные аргументы. Можно использовать частичный
вместо лямбда , чтобы предоставить аргументы по умолчанию для
функция, оставляя некоторые аргументы неуказанными.

Частичные объекты

В этом примере показаны два простых объекта частичного для
функция myfunc () . Вывод show_details () включает
func , args и ключевые слова атрибутов
частичный объект.

 import functools


def myfunc (a, b = 2):
    «Строка документации для myfunc ()».
    print ('называется myfunc с:', (a, b))


def show_details (name, f, is_partial = False):
    «Показать подробную информацию о вызываемом объекте».
    print ('{}:'. формат (имя))
    print ('объект:', f)
    если не is_partial:
        print ('__name__:', f .__ name__)
    если is_partial:
        print ('функция:', f.func)
        print ('аргументы:', f.args)
        print ('ключевые слова:', е. ключевые слова)
    возвращение


show_details ('myfunc', myfunc)
myfunc ('а', 3)
Распечатать()

# Установить другое значение по умолчанию для 'b', но потребовать
# вызывающий должен предоставить 'a'.p1 = functools.partial (myfunc, b = 4)
show_details ('частичный с именем по умолчанию', p1, True)
p1 ('прохождение')
p1 ('переопределить b', b = 5)
Распечатать()

# Установить значения по умолчанию как для 'a', так и для 'b'.
p2 = functools.partial (myfunc, 'по умолчанию a', b = 99)
show_details ('частично со значениями по умолчанию', p2, True)
p2 ()
p2 (b = 'переопределить b')
Распечатать()

print ('Недостаточно аргументов:')
p1 ()
 

В конце примера первое созданное частичное будет
вызывается без передачи значения , вызывая исключение.

 $ python3 functools_partial.py

myfunc:
  объект: <функция myfunc at 0x1007a6a60>
  __name__: myfunc
  называется myfunc с помощью: ('a', 3)

частичный с именем по умолчанию:
  объект: functools.partial (<функция myfunc at 0x1007a6a60>,
б = 4)
  func: <функция myfunc at 0x1007a6a60>
  аргументы: ()
  ключевые слова: {'b': 4}
  называется myfunc с помощью: ('прохождение', 4)
  называется myfunc с помощью: ('override b', 5)

частичный со значениями по умолчанию:
  объект: functools.partial (<функция myfunc at 0x1007a6a60>,
'default a', b = 99)
  func: <функция myfunc at 0x1007a6a60>
  args: ('по умолчанию',)
  ключевые слова: {'b': 99}
  называется myfunc с помощью: ('default a', 99)
  называется myfunc с помощью: ('default a', 'override b')

Недостаточные аргументы:
Отслеживание (последний вызов последний):
  Файл "functools_partial.py ", строка 51, в 
    p1 ()
TypeError: myfunc () отсутствует 1 обязательный позиционный аргумент: 'a'
 

Свойства функции получения

частичный объект не имеет __name__ или __doc__
атрибуты по умолчанию и без этих атрибутов, украшенные
функции труднее отлаживать. Используя update_wrapper () ,
копирует или добавляет атрибуты из исходной функции в
частичный объект.

functools_update_wrapper.ру

 import functools


def myfunc (a, b = 2):
    «Строка документации для myfunc ()».
    print ('называется myfunc с:', (a, b))


def show_details (имя, f):
    «Показать подробную информацию о вызываемом объекте».
    print ('{}:'. формат (имя))
    print ('объект:', f)
    print ('__name__:', конец = '')
    пытаться:
        print (f .__ name__)
    кроме AttributeError:
        print ('(без __name__)')
    print ('__doc__', repr (f .__ doc__))
    Распечатать()


show_details ('myfunc', myfunc)

p1 = functools.partial (myfunc, b = 4)
show_details ('сырая оболочка', p1)

print ('Обновление оболочки:')
print ('assign:', functools.WRAPPER_ASSIGNMENTS)
print ('обновление:', functools.WRAPPER_UPDATES)
Распечатать()

functools.update_wrapper (p1, myfunc)
show_details ('обновленная оболочка', p1)
 

Атрибуты, добавленные в оболочку, определены в
WRAPPER_ASSIGNMENTS , а WRAPPER_UPDATES списков
значения, которые необходимо изменить.

 $ python3 functools_update_wrapper.py

myfunc:
  объект: <функция myfunc at 0x1018a6a60>
  __name__: myfunc
  __doc__ 'Строка документации для myfunc ().'

сырая обертка:
  объект: functools.partial (<функция myfunc at 0x1018a6a60>,
б = 4)
  __name__: (нет __name__)
  __doc__ 'partial (func, * args, ** keywords) - новая функция с
частичное применение \ n данных аргументов и ключевых слов. \ n '

Обновление оболочки:
  назначить: ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
  обновление: ('__dict__',)

обновленная оболочка:
  объект: functools.partial (<функция myfunc at 0x1018a6a60>,
б = 4)
  __name__: myfunc
  __doc__ 'Строка документации для myfunc ().'
 

Другие звонки

Partials работает с любым вызываемым объектом, а не только с автономными функциями.

 import functools


класс MyClass:
    «Демонстрационный класс для functools»

    def __call __ (self, e, f = 6):
        "Строка документации для MyClass .__ call__"
        print ('вызываемый объект с помощью:', (self, e, f))


def show_details (имя, f):
    «Показать подробную информацию о вызываемом объекте».
    print ('{}:'. формат (имя))
    print ('объект:', f)
    print ('__name__:', конец = '')
    пытаться:
        print (f .__ name__)
    кроме AttributeError:
        print ('(без __name__)')
    print ('__doc__', repr (f.__doc__))
    возвращение


o = MyClass ()

show_details ('экземпляр', о)
о ('е идет сюда')
Распечатать()

p = functools.partial (o, e = 'по умолчанию для e', f = 8)
functools.update_wrapper (p, o)
show_details ('оболочка экземпляра', p)
п()
 

В этом примере создаются партиалы из экземпляра класса с
__call __ () метод.

 $ python3 functools_callable.py

пример:
  объект: <__ main __. MyClass объект в 0x1011b1cf8>
  __name__: (нет __name__)
  __doc__ 'Демонстрационный класс для functools'
  вызываемый объект с помощью: (<__ main__.Объект MyClass по адресу 0x1011b1cf8>,
'e идет сюда', 6)

обертка экземпляра:
  объект: functools.partial (<__ main __. MyClass объект в
0x1011b1cf8>, f = 8, e = 'по умолчанию для e')
  __name__: (нет __name__)
  __doc__ 'Демонстрационный класс для functools'
  вызываемый объект с помощью: (<__ main __. MyClass object at 0x1011b1cf8>,
'по умолчанию для е', 8)
 

Методы и функции

В то время как partial () возвращает вызываемый объект, готовый к непосредственному использованию,
partialmethod () возвращает вызываемый объект, готовый к использованию в качестве
несвязанный метод объекта.В следующем примере тот же
автономная функция добавляется как атрибут MyClass дважды,
один раз используя partialmethod () как method1 () и снова используя
частичное () как method2 () .

functools_partialmethod.py

 import functools


def автономный (self, a = 1, b = 2):
    «Автономная функция»
    print ('называется автономным с помощью:', (self, a, b))
    если self не None:
        print ('self.attr =', self.attr)


класс MyClass:
    «Демонстрационный класс для functools»

    def __init __ (сам):
        я.attr = 'атрибут экземпляра'

    method1 = functools.partialmethod (автономный)
    method2 = functools.partial (автономный)


o = MyClass ()

print ('автономный')
автономный (нет)
Распечатать()

print ('метод1 как частичный метод')
o.method1 ()
Распечатать()

print ('метод2 как частичный')
пытаться:
    o.method2 ()
кроме TypeError как err:
    print ('ОШИБКА: {}'. формат (ошибка))
 

method1 () можно вызвать из экземпляра MyClass , а
instance передается в качестве первого аргумента, как и в случае определенных методов
как обычно. method2 () не настроен как связанный метод, поэтому
self аргумент должен быть передан явно, иначе вызов приведет к
в TypeError .

 $ python3 functools_partialmethod.py

автономный
  называется автономным: (Нет, 1, 2)

method1 как частичный метод
  называется автономным с помощью: (<__ main __. MyClass object at
0x1007b1d30>, 1, 2)
  self.attr = атрибут экземпляра

method2 как частичный
ОШИБКА: в standalone () отсутствует 1 обязательный позиционный аргумент:
'я'
 

Получение свойств функции для декораторов

Обновление свойств обернутого вызываемого объекта особенно полезно
при использовании в декораторе, поскольку преобразованная функция заканчивается
свойства исходной «голой» функции.

 import functools


def show_details (имя, f):
    «Показать подробную информацию о вызываемом объекте».
    print ('{}:'. формат (имя))
    print ('объект:', f)
    print ('__name__:', конец = '')
    пытаться:
        print (f .__ name__)
    кроме AttributeError:
        print ('(без __name__)')
    print ('__doc__', repr (f .__ doc__))
    Распечатать()


def simple_decorator (f):
    @ functools.wraps (f)
    def Decorated (a = 'декорированные значения по умолчанию', b = 1):
        print ('украшено:', (a, b))
        печать ('', конец = '')
        вернуть f (a, b = b)
    возвращение оформлено


def myfunc (a, b = 2):
    "myfunc () не сложный"
    print ('myfunc:', (а, б))
    возвращение


# Необработанная функция
show_details ('myfunc', myfunc)
myfunc ('развернутый, по умолчанию b')
myfunc ('развернутый, передающий b', 3)
Распечатать()

# Обернуть явно
wrapped_myfunc = простой_декоратор (myfunc)
show_details ('wrapped_myfunc', wrapped_myfunc)
wrapped_myfunc ()
wrapped_myfunc ('аргументы в завернутые', 4)
Распечатать()


# Обернуть синтаксисом декоратора
@simple_decorator
def Decorated_myfunc (a, b):
    myfunc (а, б)
    возвращение


show_details ('украшенный_myfunc', украшенный_myfunc)
украшенный_myfunc ()
Decorated_myfunc ('аргументы для украшения', 4)
 

functools предоставляет декоратор, wraps () , который применяется
update_wrapper () в декорированную функцию.

 $ python3 functools_wraps.py

myfunc:
  объект: <функция myfunc at 0x101241b70>
  __name__: myfunc
  __doc__ 'myfunc () не сложный'

  myfunc: ('развернутый, по умолчанию b', 2)
  myfunc: ('развернуто, передано b', 3)

wrapped_myfunc:
  объект: <функция myfunc at 0x1012e62f0>
  __name__: myfunc
  __doc__ 'myfunc () не сложный'

  украшенный: ('декорированные значения по умолчанию', 1)
     myfunc: ('декорированные значения по умолчанию', 1)
  украшено: ('аргументы в обертку', 4)
     myfunc: ('аргументы в оболочку', 4)

украшенный_myfunc:
  объект: <функция Decorated_myfunc at 0x1012e6400>
  __name__: decor_myfunc
  __doc__ Нет

  украшенный: ('декорированные значения по умолчанию', 1)
     myfunc: ('декорированные значения по умолчанию', 1)
  украшенный: ('аргументы к украшенному', 4)
     myfunc: ('аргументы для украшения', 4)
 

Сравнение

В Python 2 классы могут определять метод __cmp __ () , который
возвращает -1 , 0 или 1 в зависимости от того, меньше ли объект
чем, равно или больше сравниваемого элемента.Python 2.1
представил богатое сравнение методов API ( __lt __ () ,
__le __ () , __eq __ () , __ne __ () , __gt __ () , и
__ge __ () ), которые выполняют одну операцию сравнения и возвращают
логическое значение. Python 3 устарел __cmp __ () в пользу
эти новые методы и функции предоставляет инструменты, позволяющие
легче писать классы, соответствующие новому сравнению
требования в Python 3.

Богатое сравнение

Богатый API сравнения предназначен для классов со сложными
сравнения для наиболее эффективного выполнения каждого теста.
Однако для классов, где сравнение относительно простое, есть
нет смысла вручную создавать каждый из богатых методов сравнения.
Декоратор класса total_ordering () принимает класс, который предоставляет
некоторые методы и добавляются остальные.

functools_total_ordering.py

 import functools
импортная инспекция
из pprint импорт pprint


@functools.total_ordering
класс MyObject:

    def __init __ (self, val):
        self.val = val

    def __eq __ (себя, другое):
        print ('тестирование __eq __ ({}, {})'. format (
            self.val, other.val))
        вернуть self.val == other.val

    def __gt __ (себя, другое):
        print ('тестирование __gt __ ({}, {})'. format (
            self.val, other.val))
        вернуть self.val> other.val


print ('Методы: \ n')
pprint (inspect.getmembers (MyObject, inspect.isfunction))

а = MyObject (1)
b = MyObject (2)

print ('\ nСравнения:')
для выражения в ['a  = b', 'a> b']:
    печать ('\ п {: <6}:'.формат (выражение))
    результат = eval (выражение)
    print ('результат {}: {}'. формат (выражение, результат))
 

Класс должен обеспечивать реализацию __eq __ () и еще одного
богатый метод сравнения. Декоратор добавляет реализации
остальные методы, которые работают с использованием предоставленных сравнений. Если
сравнение невозможно, метод должен вернуть NotImplemented
поэтому сравнение можно попробовать с помощью операторов обратного сравнения
на другом объекте, прежде чем полностью потерпеть неудачу.

 $ python3 functools_total_ordering.py

Методы:

[('__eq__', <функция MyObject .__ eq__ at 0x10139a488>),
 ('__ge__', <функция _ge_from_gt в 0x1012e2510>),
 ('__gt__', <функция MyObject .__ gt__ at 0x10139a510>),
 ('__init__', <функция MyObject .__ init__ в 0x10139a400>),
 ('__le__', <функция _le_from_gt в 0x1012e2598>),
 ('__lt__', <функция _lt_from_gt в 0x1012e2488>)]

Сравнения:

а <б:
  тестирование __gt __ (1, 2)
  тестирование __eq __ (1, 2)
  результат a  = b:
  тестирование __gt __ (1, 2)
  тестирование __eq __ (1, 2)
  результат a> = b: ложь

а> б:
  тестирование __gt __ (1, 2)
  результат a> b: ложь
 

Порядок сопоставления

Поскольку функции сравнения старого стиля в Python 3 устарели,
cmp аргумент для таких функций, как sort () , также больше не
поддерживается.Старые программы, использующие функции сравнения, могут использовать
cmp_to_key () , чтобы преобразовать их в функцию, которая возвращает
ключ сопоставления , который используется для определения позиции в финальном
последовательность.

 import functools


класс MyObject:

    def __init __ (self, val):
        self.val = val

    def __str __ (сам):
        вернуть 'MyObject ({})'. формат (self.val)


def compare_obj (a, b):
    "" "Функция сравнения в старом стиле.
    "" "
    print ('сравнение {} и {}'. формат (a, b))
    если.val  b.val:
        возврат 1
    возврат 0


# Сделать ключевую функцию с помощью cmp_to_key ()
get_key = functools.cmp_to_key (compare_obj)

def get_key_wrapper (o):
    «Функция-оболочка для get_key, позволяющая выводить операторы печати».
    new_key = get_key (о)
    print ('key_wrapper ({}) -> {! r}'. format (o, new_key))
    вернуть new_key


objs = [MyObject (x) для x в диапазоне (5, 0, -1)]

для o в отсортированном (objs, key = get_key_wrapper):
    печать (o)
 

Обычно cmp_to_key () используется напрямую, но в этом
Например, введена дополнительная функция-оболочка для вывода дополнительных
информация, как вызывается ключевая функция.

Выходные данные показывают, что sorted () начинается с вызова
get_key_wrapper () для каждого элемента в последовательности для создания
ключ. Ключи, возвращаемые функцией cmp_to_key () , являются экземплярами класса
определен в functools , который реализует богатый API сравнения
используя переданную функцию сравнения старого стиля. После всех
ключи создаются, последовательность сортируется путем сравнения ключей.

 $ python3 functools_cmp_to_key.py

key_wrapper (MyObject (5)) -> 
key_wrapper (MyObject (4)) -> <объект functools.KeyWrapper в
0x1011c5510>
key_wrapper (MyObject (3)) -> <объект functools.KeyWrapper в
0x1011c54f0>
key_wrapper (MyObject (2)) -> <объект functools.KeyWrapper в
0x1011c5390>
key_wrapper (MyObject (1)) -> <объект functools.KeyWrapper в
0x1011c5710>
сравнение MyObject (4) и MyObject (5)
сравнение MyObject (3) и MyObject (4)
сравнение MyObject (2) и MyObject (3)
сравнение MyObject (1) и MyObject (2)
МойОбъект (1)
МойОбъект (2)
МойОбъект (3)
МойОбъект (4)
МойОбъект (5)
 

Кэширование

Декоратор lru_cache () оборачивает функцию в
кэш, который использовался меньше всего.Аргументы функции используются для построения
хэш-ключ, который затем сопоставляется с результатом. Последующие звонки с
те же аргументы будут извлекать значение из кеша вместо
вызов функции. Декоратор также добавляет методы к функции
чтобы проверить состояние кеша ( cache_info () ) и очистить
кеш ( cache_clear () ).

 import functools


@ functools.lru_cache ()
def дорого (a, b):
    print ('дорого ({}, {})'. format (a, b))
    вернуть а * б


МАКС = 2

print ('Первый набор звонков:')
для i в диапазоне (MAX):
    для j в диапазоне (MAX):
        дорогой (i, j)
печать (дорого.cache_info ())

print ('\ nВторой набор вызовов:')
для i в диапазоне (MAX + 1):
    для j в диапазоне (MAX + 1):
        дорогой (i, j)
печать (дорогой.cache_info ())

print ('\ nОчистка кеша:')
дорогой.cache_clear ()
печать (дорогой.cache_info ())

print ('\ nТретий набор вызовов:')
для i в диапазоне (MAX):
    для j в диапазоне (MAX):
        дорогой (i, j)
печать (дорогой.cache_info ())
 

В этом примере выполняется несколько вызовов Road () в наборе вложенных
петли. Во второй раз эти вызовы выполняются с теми же значениями,
результаты появятся в кеше.Когда кеш очищен и петли
запускаются снова, значения необходимо пересчитать.

 $ python3 functools_lru_cache.py

Первый набор звонков:
дорого (0, 0)
дорого (0, 1)
дорого (1, 0)
дорого (1, 1)
CacheInfo (совпадения = 0, промахи = 4, maxsize = 128, currsize = 4)

Второй набор звонков:
дорого (0, 2)
дорого (1, 2)
дорого (2, 0)
дорого (2, 1)
дорого (2, 2)
CacheInfo (совпадения = 4, промахи = 9, maxsize = 128, currsize = 9)

Очистка кеша:
CacheInfo (совпадения = 0, промахи = 0, maxsize = 128, currsize = 0)

Третий набор звонков:
дорого (0, 0)
дорого (0, 1)
дорого (1, 0)
дорого (1, 1)
CacheInfo (совпадения = 0, промахи = 4, maxsize = 128, currsize = 4)
 

Для предотвращения неограниченного роста кеша при длительной работе
процесса, ему дается максимальный размер.По умолчанию 128 записей, но
который можно изменить для каждого кеша с помощью аргумента maxsize .

functools_lru_cache_expire.py

 import functools


@ functools.lru_cache (maxsize = 2)
def дорого (a, b):
    print ('называется дорогим ({}, {})'. format (a, b))
    вернуть а * б


def make_call (a, b):
    print ('({}, {})'. format (a, b), end = '')
    pre_hits = дорогое .cache_info (). hits
    дорого (а, б)
    post_hits = дорогой.cache_info (). hits
    если post_hits> pre_hits:
        print ('попадание в кеш')


print ('Установить кеш')
make_call (1, 2)
make_call (2, 3)

print ('\ nИспользовать кешированные элементы')
make_call (1, 2)
make_call (2, 3)

print ('\ nВычислить новое значение, вызывая истечение срока действия кеша')
make_call (3, 4)

print ('\ nCache все еще содержит один старый элемент')
make_call (2, 3)

print ('\ nСтарый элемент необходимо пересчитать')
make_call (1, 2)
 

В этом примере размер кэша установлен на 2 записи.Когда третий сет
уникальных аргументов ( 3, 4 ) используется самый старый элемент в кеше
упал и заменил на новый результат.

 $ python3 functools_lru_cache_expire.py

Установить кеш
(1, 2) называется дорогим (1, 2)
(2, 3) называется дорогим (2, 3)

Использовать кешированные элементы
(1, 2) попадание в кеш
(2, 3) попадание в кеш

Вычислить новое значение, вызывая истечение срока действия кеша
(3, 4) называются дорогими (3, 4)

Кеш по-прежнему содержит один старый элемент
(2, 3) попадание в кеш

Необходимо пересчитать самый старый элемент
(1, 2) называется дорогим (1, 2)
 

Ключи для кэша, управляемого lru_cache () , должны быть хешируемыми,
поэтому все аргументы функции заключены в кеш-поиск
должен быть хешируемым.

functools_lru_cache_arguments.py

 import functools


@ functools.lru_cache (maxsize = 2)
def дорого (a, b):
    print ('называется дорогим ({}, {})'. format (a, b))
    вернуть а * б


def make_call (a, b):
    print ('({}, {})'. format (a, b), end = '')
    pre_hits = дорогое .cache_info (). hits
    дорого (а, б)
    post_hits = дорогой.cache_info (). hits
    если post_hits> pre_hits:
        print ('попадание в кеш')


make_call (1, 2)

пытаться:
    make_call ([1], 2)
кроме TypeError как err:
    print ('ОШИБКА: {}'.формат (ошибка))

пытаться:
    make_call (1, {'2': 'два'})
кроме TypeError как err:
    print ('ОШИБКА: {}'. формат (ошибка))
 

Если какой-либо объект, который не может быть хеширован, передается в
функция вызывает TypeError .

 $ python3 functools_lru_cache_arguments.py

(1, 2) называется дорогим (1, 2)
([1], 2) ОШИБКА: нехешируемый тип: 'список'
(1, {'2': 'two'}) ОШИБКА: нехэшируемый тип: 'dict'
 

Сокращение набора данных

Функция reduce () принимает вызываемый объект и последовательность данных как
input и производит одно значение в качестве вывода на основе вызова
вызывается значениями из последовательности и накапливает
результирующий вывод.

 import functools


def do_reduce (a, b):
    print ('do_reduce ({}, {})'. format (a, b))
    вернуть a + b


данные = диапазон (1, 5)
печать (данные)
результат = functools.reduce (do_reduce, данные)
print ('результат: {}'. формат (результат))
 

В этом примере складываются числа во входной последовательности.

 $ python3 functools_reduce.py

диапазон (1, 5)
do_reduce (1, 2)
do_reduce (3, 3)
do_reduce (6, 4)
результат: 10
 

Необязательный аргумент инициализатора помещается в начало
последовательность и обрабатывается вместе с другими элементами.Это можно использовать для
обновить ранее вычисленное значение новыми входными данными.

functools_reduce_initializer.py

 import functools


def do_reduce (a, b):
    print ('do_reduce ({}, {})'. format (a, b))
    вернуть a + b


данные = диапазон (1, 5)
печать (данные)
результат = functools.reduce (do_reduce, данные, 99)
print ('результат: {}'. формат (результат))
 

В этом примере предыдущая сумма 99 используется для инициализации
значение, вычисленное с помощью reduce () .

 $ python3 functools_reduce_initializer.ру

диапазон (1, 5)
do_reduce (99, 1)
do_reduce (100, 2)
do_reduce (102; 3)
do_reduce (105, 4)
результат: 109
 

Последовательности с одним элементом автоматически уменьшаются до этого значения, когда
инициализатора нет. Пустые списки вызывают ошибку, если только
предоставляется инициализатор.

functools_reduce_short_sequences.py

 import functools


def do_reduce (a, b):
    print ('do_reduce ({}, {})'. format (a, b))
    вернуть a + b


print ('Один элемент в последовательности:',
      functools.reduce (do_reduce, [1]))

print ('Один элемент в последовательности с инициализатором:',
      functools.уменьшить (do_reduce, [1], 99))

print ('Пустая последовательность с инициализатором:',
      functools.reduce (do_reduce, [], 99))

пытаться:
    print ('Пустая последовательность:', functools.reduce (do_reduce, []))
кроме TypeError как err:
    print ('ОШИБКА: {}'. формат (ошибка))
 

Поскольку аргумент инициализатора используется по умолчанию, но также
в сочетании с новыми значениями, если входная последовательность не пуста, она
важно тщательно обдумать, использовать ли его. Когда это не так
имеет смысл объединить значения по умолчанию с новыми значениями, лучше
поймать TypeError вместо передачи инициализатора.

 $ python3 functools_reduce_short_sequences.py

Один элемент в последовательности: 1
do_reduce (99, 1)
Один элемент в последовательности с инициализатором: 100
Пустая последовательность с инициализатором: 99
ОШИБКА: reduce () пустой последовательности без начального значения
 

Общие функции

В динамически типизированном языке, таком как Python, обычно требуется
выполнять несколько иную операцию в зависимости от типа аргумента,
особенно когда имеешь дело с разницей между списком предметов
и один предмет.Достаточно просто проверить тип
аргумент напрямую, но в случаях, когда поведенческие различия могут быть
изолированы на отдельные функции functools обеспечивает
singledispatch () декоратор для регистрации набора из универсального
функции
для автоматического переключения в зависимости от типа первого
аргумент функции.

functools_singledispatch.py

 import functools


@ functools.singledispatch
def myfunc (аргумент):
    print ('default myfunc ({! r})'. format (arg))


@myfunc.регистр (число)
def myfunc_int (аргумент):
    print ('myfunc_int ({})'. формат (аргумент))


@ myfunc.register (список)
def myfunc_list (аргумент):
    print ('myfunc_list ()')
    для элемента в аргументе:
        печать ('{}'. формат (элемент))


myfunc ('строковый аргумент')
myfunc (1)
myfunc (2.3)
myfunc (['a', 'b', 'c'])
 

Атрибут register () новой функции служит еще одним
декоратор для регистрации альтернативных реализаций. Первый
функция, заключенная в singledispatch () по умолчанию
реализация, если не найдена другая функция для конкретного типа, как с
случай float в этом примере.

 $ python3 functools_singledispatch.py

по умолчанию myfunc ('строковый аргумент')
myfunc_int (1)
по умолчанию myfunc (2.3)
myfunc_list ()
  а
  б
  c
 

Если для типа не найдено точное совпадение, порядок наследования
оценивается и используется наиболее подходящий тип.

functools_singledispatch_mro.py

 import functools


класс А:
    проходить


класс B (A):
    проходить


класс C (A):
    проходить


класс D (B):
    проходить


класс E (C, D):
    проходить


@ functools.singledispatch
def myfunc (аргумент):
    print ('default myfunc ({})'.формат (аргумент .__ класс __.__ имя__))


@ myfunc.register (А)
def myfunc_A (аргумент):
    print ('myfunc_A ({})'. format (аргумент .__ класс __.__ имя__))


@ myfunc.register (B)
def myfunc_B (аргумент):
    print ('myfunc_B ({})'. format (аргумент .__ класс __.__ имя__))


@ myfunc.register (C)
def myfunc_C (аргумент):
    print ('myfunc_C ({})'. format (аргумент .__ класс __.__ имя__))


myfunc (A ())
myfunc (B ())
myfunc (C ())
myfunc (D ())
myfunc (E ())
 

В этом примере классы D и E не совпадают точно ни с одним
зарегистрированные универсальные функции, и выбранная функция зависит от
иерархия классов.

 $ python3 functools_singledispatch_mro.py

myfunc_A (А)
myfunc_B (B)
myfunc_C (C)
myfunc_B (D)
myfunc_C (E)
 

.

Python `functools.wraps` неправильно обрабатывает значения по умолчанию

Переполнение стека

  1. Около
  2. Продукты

  3. Для команд
  1. Переполнение стека
    Общественные вопросы и ответы

  2. Переполнение стека для команд
    Где разработчики и технологи делятся частными знаниями с коллегами

  3. Вакансии
    Программирование и связанные с ним технические возможности карьерного роста

  4. Талант
    Нанимайте технических специалистов и создавайте свой бренд работодателя

  5. Реклама
    Обратитесь к разработчикам и технологам со всего мира

  6. О компании

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *