python黑魔法——装饰器

本文深入讲解Python装饰器的原理及应用,包括基本装饰器、带参数装饰器、装饰器调用顺序等内容,并介绍Python内置装饰器如@property、@staticmethod、@classmethod的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、简单的例子

使用@装饰符(下面的@deco等同于myfunc = deco(myfunc))

def deco(func):
    print("step in deco")
    func()
    print("leave out deco")
    return func

@deco
def myfunc():
    print("myfunc() called")

myfunc()
myfunc()

输出:
>>> step in deco
    myfunc() called
    leave out deco
    myfunc() called
    myfunc() called

可以发现装饰器中的内容只执行了一次,而myfunc函数却多执行了一次(其实是在@deco这句执行的),这是为什么呢?回到之前说的@deco等同于myfunc = deco(myfunc),此时@deco执行了一次myfunc,并且返回myfunc, 也就是说其实是执行了myfunc = myfunc(myfunc被赋值为自己本身),所以并没有达到装饰的效果。
其实我们想要的效果是myfunc = deco 而非 myfunc = deco(myfunc)

改进的方法:装饰器加入内层函数

def deco(func):
    def wrapper():
        print("step in deco")
        func()
        print("leave out deco")
    return wrapper

@deco
def myfunc():
    print("myfunc() called")

myfunc()
myfunc()

输出:
>>> step in deco
    myfunc() called
    leave out deco
    step in deco
    myfunc() called
    leave out deco

这时候,@deco并没有执行myfunc,而是返回带有func参数闭包的wrapper函数的引用,这是我们调用myfunc的时候就相当于调用了wrapper,就起到了装饰的效果。


二、修饰带参数和存在返回值的函数

def deco(func):
    def wrapper(a, b):
        print("step in deco")
        ret = func(a, b)
        print("leave out deco")
        return ret
    return wrapper

@deco
def myfunc(a, b):
    print("myfunc called")
    return a + b

print(myfunc(3, 5))

输出:
>>> step in deco
    myfunc called
    leave out deco
    8

@装饰符的意义和上面的简单例子相同,只是在内层wrapper函数加入了参数和返回值


三、带参数的装饰器

有时候我们想让一个装饰器有多种装饰功能,在使用@装饰符的时候通过向装饰器传递参数以指明调用哪种装饰功能

import time

def deco(arg):
    def _deco(func):
        def __deco():
            if arg == 'year':
                print("the year is: {0}".format(time.localtime(time.time()).tm_year))
            elif arg == 'month':
                print("the month is: {0}".format(time.localtime(time.time()).tm_mon))
            elif arg == 'day':
                print("the day is: {0}".format(time.localtime(time.time()).tm_mday))
            else:
                print("the time is: {0}".format(time.ctime()))
            func()
        return __deco
    return _deco

@deco("year")
def myfunc():
    print("myfunc() called")

@deco("month")
def myfunc2():
    print("myfunc2() called")

@deco("")
def myfunc3():
    print("myfunc3() called")

myfunc()
myfunc2()
myfunc3()

输出:
>>> the year is: 2018
    myfunc() called
    the month is: 6
    myfunc2() called
    the time is: Wed Jun  6 01:26:33 2018
    myfunc3() called

同理,此处的@deco(“year”)等同于myfunc = deco(“year”)(myfunc),其实最终等同于myfunc = __deco,所以需要三层函数来实现


四、装饰器调用顺序

import time

def deco(arg):
    def _deco(func):
        def __deco():
            if arg == 'year':
                print("the year is: {0}".format(time.localtime(time.time()).tm_year))
            elif arg == 'month':
                print("the month is: {0}".format(time.localtime(time.time()).tm_mon))
            elif arg == 'day':
                print("the day is: {0}".format(time.localtime(time.time()).tm_mday))
            else:
                print("the time is: {0}".format(time.ctime()))
            func()
        return __deco
    return _deco

@deco("year")
@deco("month")
@deco("day")
def myfunc():
    print("myfunc called")

myfunc()

输出:
>>> the year is: 2018
    the month is: 6
    the day is: 6
    myfunc called

再来看另一个例子:

def deco1(func):
    print("step in deco1")
    def wrapper1():
        print("step in wrapper1")
        func()
        print("leave out wrapper1")
    print("leave out deco1")
    return wrapper1

def deco2(func):
    print("step in deco2")
    def wrapper2():
        print("step in wrapper2")
        func()
        print("leave out wrapper2")
    print("leave out deco2")
    return wrapper2

def deco3(func):
    print("step in deco3")
    def wrapper3():
        print("step in wrapper3")
        func()
        print("leave out wrapper3")
    print("leave out deco3")
    return wrapper3

@deco1
@deco2
@deco3
def myfunc():
    print("myfunc() called")

myfunc()

输出:
>>> step in deco3
    leave out deco3
    step in deco2
    leave out deco2
    step in deco1
    leave out deco1
    step in wrapper1
    step in wrapper2
    step in wrapper3
    myfunc() called
    leave out wrapper3
    leave out wrapper2
    leave out wrapper1

可以看到,调用装饰器的顺序和@装饰符的声明顺序相反的,但是调用wrapper的顺序却和装饰符的声明顺序相同。此处等同于myfunc = deco3(deco2(deco1(myfunc))),其实就是内层函数wrapper3装饰了myfunc,wrapper2装饰了wrapper3, wrapper1装饰了wrapper2.


五、python内置的装饰器

在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。

  • staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
  • classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
  • property 是属性的意思,表示可以通过通过类实例直接访问的信息(可以用其实现C#的get和set方法)

三者的具体用法:

@property

使调用类中的方法像引用类中的字段属性一样。被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式。遵循了统一访问的原则。
property函数原型为property(fget=None,fset=None,fdel=None,doc=None)
示例:

# coding: utf-8
class TestClass:
    name = "test"

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

    @property
    def sayHello(self):
        print "hello", self.name

cls = TestClass("felix")
print "通过实例引用属性"
print cls.name
print "像引用属性一样调用@property修饰的方法"
cls.sayHello

实现类似java和C#get的功能

class Boy(object):
    def __init__(self, name):
        self.name = name

        @property
        def name(self):
            return self.name

        # 下面这个装饰器是由上面的@property衍生出来的装饰器
        @name.setter
        def name(self, new_name):
            self.name = new_name

        # 下面这个装饰器是由上面的@property衍生出来的装饰器
        @name.deleter
        def del_name(self):
            del self.name

# 实例化
boy = Boy('Tom')

# 更新name(set方法)
boy.name = 'Alice'
# 获得'Alice'(get方法)
print(boy.name) 
# 删除name(del方法)
del boy.name
@staticmethod

将类中的方法装饰为静态方法,即类不需要创建实例的情况下,可以通过类名直接引用。到达将函数功能与实例解绑的效果。

class TestClass:
    name = "test"

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

    @staticmethod
    def fun(self, x, y):
        return  x + y

cls = TestClass("felix")
print ("通过实例引用方法")
print (cls.fun(None, 2, 3)) # 参数个数必须与定义中的个数保持一致,否则报错
print ("类名直接引用静态方法")
print (TestClass.fun(None, 2, 3)) # 参数个数必须与定义中的个数保持一致,否则报错


输出:
>>> 通过实例引用方法
    5
    类名直接引用静态方法
    5
@classmethod

类方法的第一个参数是一个类,是将类本身作为操作的方法。类方法被哪个类调用,就传入哪个类作为第一个参数进行操作。

class Car(object):
    car = "audi"

    @classmethod
    def value(self, category): # 可定义多个参数,但第一个参数为类本身
        print ("{0} car of {1}".format(category, self.car))

class BMW(Car):
    car = "BMW"

class Benz(Car):
    car = "Benz"

print ("通过实例调用")
baoma = BMW()
baoma.value("Normal") # 由于第一个参数为类本身,调用时传入的参数对应的时category

print ("通过类名直接调用")
Benz.value("SUV")


输出:
>>> 通过实例调用
    Normal car of BMW
    通过类名直接调用
    SUV car of Benz
<think>好的,用户想自学Python编程,询问入门教程。我需要根据提供的引用内容来组织回答,确保合系统级指令的要求。 首先,用户需要的是入门教程,引用[2]提到了400集的免费教程,适合从零开始学习,应该优先推荐。同时引用[1]中的《Python中文指南》是面向初学者的零基础教程,建议通读,这也是一个重点。 接下来,用户可能需要了解如何配置开发环境。引用[4]提到开发软件和环境配置的教程,这里可以推荐Anaconda和PyCharm,引用[1]里的《PyCharm 中文指南》能帮助用户更好地使用这个IDE。 然后,关于科学计算和数据分析,引用[3]介绍了使用Anaconda和Jupyter Notebook的内容,适合进阶学习,可以作为后续推荐。 还要确保回答结构清晰,分步骤给出建议,比如先基础教程,再开发环境,然后实践项目,最后进阶内容。同时,每个部分末尾自然地添加引用标识。 需要检查是否所有数学公式都用了正确的格式,虽然当前问题可能不涉及数学,但如果有变量或代码,要正确使用代码块。例如Python代码示例需要用```python包裹。 最后生成相关问题,要围绕Python自学路径、开发工具选择、免费资源、科学计算等方面提问,确保问题相关且有参考价值。 现在整理回答结构: 1. 推荐基础教程:引用[2]和[1]中的《Python中文指南》 2. 开发环境配置:引用[4]和[1]中的PyCharm指南 3. 科学计算和数据分析:引用[3] 4. 实践项目:引用[1]的《Python Cookbook》和《Python 黑魔法指南》 5. 注意事项:学习路径、练习、社区支持 然后生成相关问题,覆盖教程选择、开发工具、免费资源、科学计算、实践项目等。 确保引用正确,每个引用后的标识对应正确的来源。</think>### Python 编程自学入门指南 #### 1. **零基础入门教程推荐** - **《Python中文指南》**:专为初学者设计,内容涵盖基础语法、常用库和实战案例,建议通读并配合练习[^1]。 - **免费视频教程**:B站等平台提供400集免费教程,适合系统性学习(例如变量定义、循环结构等基础概念)[^2]。 #### 2. **开发环境配置** - **Anaconda**:集成Python解释器、Jupyter Notebook和常用科学计算库,适合数据分析场景[^3]。 - **PyCharm**:专业Python IDE,通过《PyCharm 中文指南》可掌握调试、版本控制等高级功能[^4]。 ```python # 示例:Python基础语法 print("Hello, World!") ``` #### 3. **科学计算与数据分析** - **《自学Python——编程基础、科学计算及数据分析》**:使用Anaconda环境,结合IPython和Jupyter Notebook讲解数据处理方法[^3]。 #### 4. **实战项目与进阶** - **《Python Cookbook》**:学习高级特性(如装饰器、生成器)和开发技巧。 - **《Python 黑魔法指南》**:积累100+实用技巧(例如列表推导式优化、上下文管理器)[^1]。 #### 5. **注意事项** - **学习路径**:先掌握基础语法 → 熟悉开发工具 → 完成小项目 → 深入特定领域(如Web开发、AI)。 - **练习与社区**:通过LeetCode、Kaggle练习代码,参与GitHub开源项目提升实战能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值