在 Python 中,函数是第一类对象

函数在 Python 中是第一类对象,这意味着:

函数是对象--它们可以被引用,传递到一个变量,也可以从其他函数返回。 函数可以被定义在另一个函数中--一个内部函数--也可以作为参数传递给另一个函数。

def say_name():
    print("Guido van Rossum")


def say_nationality():
    print("Netherlands")


def say(func):
    return func


say(say_name)()
say(say_nationality)()
Output:

Guido van Rossum
Netherlands

这里我们将两个不同的函数引用(不是括号内的)作为参数发送给say函数,该函数再次返回引用。

这就是内部函数的工作方式。

def say():

    def say_name():
        print("Guido van Rossum")

    def say_nationality():
        print("Netherlands")

    say_name()
    say_nationality()

say()
Output:

Guido van Rossum
Netherlands

装饰器

你已经看到函数就像Python中的其他对象一样,现在让我们举个例子来看看Python装饰器的魔力。


def say(func):

    def employer():
        print("说说你的事。")

    def say_name():
        print("我的名字是Guido van Rossum。")

    def say_nationality():
        print("我来自荷兰。")

    def wrapper():
         employer() #雇主()
        say_name()
        say_nationality()
        func()

    return wrapper

@say
def start_interview():
    print("真正的面试开始了...")

start_interview()

输出:

说些关于你的事情。
我的名字是Guido van Rossum。
我来自荷兰。
真正的面试开始了...

这里当我们调用start_interview方法时。它进入装饰函数say,定义了雇主、say_name、say_nationality和包装函数,最后它返回包装函数的引用,并在调用者函数start_interview的地方调用它。

用参数装饰函数

我们还可以装饰一个带参数的函数。我们可以在封装函数中使用*args**kwargs接受这些参数。


def say(func):

    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper

@say
def greet(name):
    print("Hello {}".format(name))

greet("Goutom")
输出:

Hello Goutom

从装饰过的函数返回 当你的装饰函数返回一些东西时会发生什么?就是从包装器中返回调用者函数。让我们看一个例子来理解这一点。

def my_decorator(func):

    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def doubled(number):
    return number*2

print(doubled(10))
Output:

20

自检

在Python中,自省是指一个对象在运行时了解其自身属性的能力。例如,一个函数知道它自己的名字和文档。让我们打印 doubled 函数的名字。

print(doubled.__name__)
Output:

wrapper

但它应该是被加倍的。在被装饰后,我们的双倍函数对其身份产生了混淆,因为它是从 wrapper 函数中被调用的。为了解决这个问题,装饰器应该在wrapper函数中使用@functools.wraps装饰器,这将保留原函数的信息。在添加这个装饰器后,它将加倍返回其原始名称。

一个现实世界的例子

让我们写一个装饰器,它将计算函数的执行时间(以秒为单位)并在控制台中打印出来。

import functools
import time


def timer(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        value = func(*args, **kwargs)
        end_time = time.perf_counter()
        run_time = end_time - start_time
        print("Finished {} in {} secs".format(repr(func.__name__), round(run_time, 3)))
        return value

    return wrapper
Now let's use this decorator:

@timer
def doubled_and_add(num):
    res = sum([i*2 for i in range(num)])
    print("Result : {}".format(res))

doubled_and_add(100000)
doubled_and_add(1000000)
Output:

Result : 9999900000
Finished ‘doubled_and_add’ in 0.0119 secs
Result : 999999000000
Finished ‘doubled_and_add’ in 0.0897 secs

现在我们来使用这个装饰器。

注册插件

装饰器不一定要包装它们所装饰的函数。他们也可以简单地注册一个函数的存在,并返回它的非包装。例如,这可以用来创建一个轻量级的插件架构。

PLUGINS = dict()

def register(func):
    PLUGINS[func.__name__] = func
    return func

@register
def add(a, b):
    return a+b

@register
def multiply(a, b):
    return a*b

def operation(func_name, a, b):
    func = PLUGINS[func_name]
    return func(a, b)

print(PLUGINS)
print(operation('add', 2, 3))
print(operation('multiply', 2, 3))
Output :

{‘add’: <function add at 0x7fb27f7a8620>, ‘multiply’: <function multiply at 0x7fb27f7a88c8>}
5
6

@register装饰器只是在全局PLUGINS dict中存储了一个对被装饰函数的引用。注意,在这个例子中,你不需要写一个内部函数或使用@functools.wraps,因为你返回的是未经修改的原始函数。

这种简单的插件架构的主要好处是,你不需要维护一个存在哪些插件的列表。这个列表是在插件自己注册时创建的。这使得添加一个新的插件变得非常简单:只需定义函数并使用@register来装饰它。

装饰类

有两种不同的方法可以在类上使用装饰器。第一种是通过装饰类的方法或装饰整个类。

几个内置的类装饰器

一些内置在 Python 中的常用装饰器是 @classmethod, @staticmethod, 和 @property。@classmethod 和 @staticmethod 装饰器用于定义类命名空间内的方法,这些方法与该类的特定实例没有关系。@property装饰器用于定制类属性的getters和setters。展开下面的方框,可以看到一个使用这些装饰器的例子。你可以在这里了解更多关于内置装饰器的信息。

让我们看一个例子。

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        ""获取半径的值""
        return self._radius

    @radius.setter
    def radius(self, value):
        ""设置半径,如果是负值,则引发错误"""
        if value >=0:
            self._radius = value
        else:
            raise ValueError("
半径必须为正")

    @property
    def area(self):
        "
"计算圆内的面积""
        return self.pi() * self.radius**2

    def cylinder_volume(self, height):
        "
"计算以圆为底的圆柱体的体积""
        return self.面积 * 高度

    @classmethod
    def unit_circle(cls):
        "
"创建半径为1的圆的工厂方法""
        return cls(1)

    @classmethod
    def pi():
        "
"π的值,不过可以用math.pi代替""
        return 3.1415926535

在这个类中:

.cylinder_volume()是一个常规方法。

.radius是一个可变的属性:它可以被设置成不同的值。

然而,通过定义一个setter方法,我们可以做一些错误测试以确保它不会被设置为一个无意义的负数。属性作为属性被访问,没有括号。

.area是一个不可变的属性:没有.setter()方法的属性不能被改变。即使它被定义为一个方法,它也可以作为一个属性被检索,不需要括号。

.unit_circle()是一个类方法。它不与Circle的一个特定实例绑定。类方法经常被用作工厂方法,可以创建该类的特定实例。

.pi()是一个静态方法。它并不真正依赖于Circle类,除了它是其命名空间的一部分。静态方法可以在一个实例或类上调用。

在控制台中测试。


>> c = Circle(5)
>>> c.radius
5

>> c.面积
78.5398163375

>> c.半径=2
>> c.面积
12.566370614

>>> c.面积=100
AttributeError: 不能设置属性

>> c.圆柱体_体积(高度=4)
50.265482456

>> c.radius = -1
ValueError。半径必须是正数

>> c = Circle.unit_circle()
>> c.radius
1

>> c.pi()
3.1415926535

>>> Circle.pi()
3.1415926535

装饰一个类方法

这里我们使用之前创建的定时器装饰器。


class Calculator:

    def __init__(self, num):
        self.num = num

    @timer
    def doubled_and_add(self):
        res = sum([i * 2 for i in range(self.num)])
        print("Result : {}".format(res))

c = Calculator(10000)
c.doubled_and_add()
Output:

Result : 99990000
Finished 'doubled_and_add' in 0.001 secs
Decorate a class
@timer
class Calculator:

    def __init__(self, num):
        self.num = num
        import time
        time.sleep(2)

    def doubled_and_add(self):
        res = sum([i * 2 for i in range(self.num)])
        print("Result : {}".format(res))

c = Calculator(100)
Output:

Finished 'Calculator' in 2.001 secs

装饰一个类并不能装饰它的方法。在这里,@timer只测量实例化类的时间。

嵌套装饰器

def hello(func):
    def wrapper():
        print("Hello")
        func()
    return wrapper

def welcome(func):

    def wrapper():
        print("Welcome")
        func()
    return wrapper

@hello
@welcome
def say():
    print("Greeting Dome")

say()
Output:

Hello
Welcome
Greeting Dome

把这看成是装饰器按照它们列出的顺序被执行。换句话说,@hello调用@welcome,后者调用say()

带有参数的装饰器

这里repeat处理装饰器的参数。


def repeat(*args_, **kwargs_):

    def inner_function(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(args_[0]):
                func(*args, **kwargs)
        return wrapper

    return inner_function


@repeat(4)
def say(name):
    print(f "Hello {name}")

say("World")

输出:


Hello World
Hello World
Hello World
Hello World

你好,世界 你好,世界 你好,世界 你好,世界

有状态的装饰器

我们可以使用一个装饰器来跟踪状态。作为一个简单的例子,我们将创建一个装饰器来计算一个函数被调用的次数。


def count_calls(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        wrapper.num_calls += 1
        print(f"Call {wrapper.num_calls} of {func.__name__!r}")
        return func(*args, **kwargs)

    wrapper.num_calls = 0
    return wrapper


@count_calls
def say():
    print("Hello!")

say()
say()
say()
say()
print(say.num_calls)

输出:


Call 1 of 'say'
Hello!
Call 2 of 'say'
Hello!
Call 3 of 'say'
Hello!
Call 4 of 'say'
Hello!
4

对'say'的调用1

你好!

调用'say'的第2次

你好!

调用'say'的3

你好!

呼叫4的 "说"。

你好!

4

对该函数的调用次数存储在包装函数的函数属性num_calls中。

作为装饰器的类

维护状态的最好方法是使用类。如果我们想使用类作为装饰器,它需要在其 .init() 方法中把 func 作为一个参数。此外,这个类需要是可调用的,这样它就可以代替被装饰的函数。为了使一个类可以被调用,我们实现了特殊的 .call() 方法。


class CountCalls。
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f "调用{self.num_calls}的{self.func.__name__!r}")
        return self.func(*args, **kwargs)


@CountCalls
def say():
    print("Hello!")

say()
say()
say()
say()
print(say.num_calls)

输出过程:

对'say'的调用1 你好! 调用'say'的第2次 你好! 调用'say'的3 你好! 呼叫4的 "说"。 你好! 4

带参数的基于类的装饰器


class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__!r}")
        return self.func(*args, **kwargs)


@CountCalls
def say():
    print("Hello!")

say()
say()
say()
say()
print(say.num_calls)
Output:

Call 1 of 'say'
Hello!
Call 2 of 'say'
Hello!
Call 3 of 'say'
Hello!
Call 4 of 'say'
Hello!
4
Class-Based Decorators with Arguments
class ClassDecorator(object):

    def __init__(self, arg1, arg2):
        print("Arguements of decorator %s, %s" % (arg1, arg2))
        self.arg1 = arg1
        self.arg2 = arg2

    def __call__(self, func):
        functools.update_wrapper(self, func)

        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

@ClassDecorator("arg1""arg2")
def print_args(*args):
    for arg in args:
        print(arg)

print_args(1, 2, 3)
Output:

Arguements of decorator arg1, arg2
1
2
3

几个实例扩展和基础用法总结。

本文由 mdnice 多平台发布

猜你喜欢

转载自blog.csdn.net/qq_40523298/article/details/127556212