python装饰器详解

有的时候,我们会在python代码中看到@符号,这其实就代表着装饰器。

本文的参考链接:

Python 装饰器

装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。大多数初学者不知道在哪儿使用它们,所以我将要分享下,哪些区域里装饰器可以让你的代码更简洁。 首先,让我们讨论下如何写你自己的装饰器。

这可能是最难掌握的概念之一。我们会每次只讨论一个步骤,这样你能完全理解它。

一切皆对象

首先我们来理解下 Python 中的函数:

def hi(name="yasoob"):
    return "hi " + name
print(hi())

hi yasoob

# 我们甚至可以将一个函数赋值给一个变量,比如
greet = hi
# 我们这里没有在使用小括号,因为我们并不是在调用hi函数
# 而是在将它放在greet变量里头。我们尝试运行下这个
 
print(greet())

hi yasoob

# 如果我们删掉旧的hi函数,看看会发生什么!
del hi
print(hi())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-87-2da5480cc9d7> in <module>
      1 # 如果我们删掉旧的hi函数,看看会发生什么!
      2 del hi
----> 3 print(hi())

NameError: name 'hi' is not defined
print(greet())
del greet

hi yasoob

在函数中定义函数

刚才那些就是函数的基本知识了。我们来让你的知识更进一步。在 Python 中我们可以在一个函数中定义另一个函数:

def hi(name="yasoob"):
    print("now you are inside the hi() function")
 
    def greet():
        return "now you are in the greet() function"
 
    def welcome():
        return "now you are in the welcome() function"
 
    print(greet())
    print(welcome())
    print("now you are back in the hi() function")

hi()

now you are inside the hi() function
now you are in the greet() function
now you are in the welcome() function
now you are back in the hi() function

# 上面展示了无论何时你调用hi(), greet()和welcome()将会同时被调用。
# 然后greet()和welcome()函数在hi()函数之外是不能访问的,比如:
 
greet()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-91-c2f1a05b0320> in <module>
      2 # 然后greet()和welcome()函数在hi()函数之外是不能访问的,比如:
      3 
----> 4 greet()

NameError: name 'greet' is not defined

那现在我们知道了可以在函数中定义另外的函数。也就是说:我们可以创建嵌套的函数。现在你需要再多学一点,就是函数也能返回函数。

从函数中返回函数

其实并不需要在一个函数里去执行另一个函数,我们也可以将其作为输出返回出来:

def hi(name="yasoob"):
    def greet():
        return "now you are in the greet() function"
 
    def welcome():
        return "now you are in the welcome() function"
 
    if name == "yasoob":
        return greet
    else:
        return welcome
 
a = hi()
print(a)

<function hi..greet at 0x0000026B12086158>

#上面清晰地展示了`a`现在指向到hi()函数中的greet()函数
#现在试试这个
 
print(a())

now you are in the greet() function

再次看看这个代码。在 if/else 语句中我们返回 greet 和 welcome,而不是 greet() 和 welcome()。为什么那样?这是因为当你把一对小括号放在后面,这个函数就会执行;然而如果你不放括号在它后面,那它可以被到处传递,并且可以赋值给别的变量而不去执行它。 你明白了吗?让我再稍微多解释点细节。

当我们写下 a = hi(),hi() 会被执行,而由于 name 参数默认是 yasoob,所以函数 greet 被返回了。如果我们把语句改为 a = hi(name = “ali”),那么 welcome 函数将被返回。我们还可以打印出 hi()(),这会输出 now you are in the greet() function。

a = hi(name='ali')
print(a)

<function hi..welcome at 0x0000026B1209FBF8>

hi()()

‘now you are in the greet() function’

将函数作为参数传给另一个函数

def hi():
    return "hi yasoob!"
 
def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi()")
    print(func())
 
doSomethingBeforeHi(hi)

I am doing some boring work before executing hi()
hi yasoob!

现在你已经具备所有必需知识,来进一步学习装饰器真正是什么了。装饰器让你在一个函数的前后去执行代码。

你的第一个装饰器

在上一个例子里,其实我们已经创建了一个装饰器!现在我们修改下上一个装饰器,并编写一个稍微更有用点的程序:

def a_new_decorator(a_func):
 
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
 
        a_func()
 
        print("I am doing some boring work after executing a_func()")
 
    return wrapTheFunction
 
def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")
 
a_function_requiring_decoration()

print(a_function_requiring_decoration.__name__)

I am the function which needs some decoration to remove my foul smell
a_function_requiring_decoration

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
 
a_function_requiring_decoration()

I am doing some boring work before executing a_func()
I am the function which needs some decoration to remove my foul smell
I am doing some boring work after executing a_func()

你看明白了吗?我们刚刚应用了之前学习到的原理。这正是 python 中装饰器做的事情!它们封装一个函数,并且用这样或者那样的方式来修改它的行为。现在你也许疑惑,我们在代码里并没有使用 @ 符号?那只是一个简短的方式来生成一个被装饰的函数。这里是我们如何使用 @ 来运行之前的代码:

@a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")
 
a_function_requiring_decoration()

I am doing some boring work before executing a_func()
I am the function which needs some decoration to remove my foul smell
I am doing some boring work after executing a_func()

@a_new_decorator实际上就是a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)的简写

希望你现在对 Python 装饰器的工作原理有一个基本的理解。

现在,如果我们运行如下代码会存在一个问题:

print(a_function_requiring_decoration.__name__)

wrapTheFunction

这并不是我们想要的!Ouput输出应该是"a_function_requiring_decoration"。这里的函数被warpTheFunction替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:

from functools import wraps
 
def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction
 
@a_new_decorator
def a_function_requiring_decoration():
    """Hey yo! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")
 
print(a_function_requiring_decoration.__name__)

a_function_requiring_decoration

现在好多了。我们接下来学习装饰器的一些常用场景。

装饰器的蓝本规范:

from functools import wraps
def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated
 
@decorator_name
def func():
    return("Function is running")

can_run = True
print(func())

Function is running

can_run = False
print(func())

Function will not run

注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。

使用场景

现在我们来看一下装饰器在哪些地方特别耀眼,以及使用它可以让一些事情管理起来变得更简单。

1.授权(Authorization)

装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:

from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

2.日志(Logging)

日志是装饰器运用的另一个亮点。这是个例子:

from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
 
result = addition_func(4)

addition_func was called

我敢肯定你已经在思考装饰器的一个其他聪明用法了。

带参数的装饰器

来想想这个问题,难道@wraps不也是个装饰器吗?但是,它接收一个参数,就像任何普通的函数能做的那样。那么,为什么我们不也那样做呢? 这是因为,当你使用@my_decorator语法时,你是在应用一个以单个函数作为参数的一个包裹函数。记住,Python里每个东西都是一个对象,而且这包括函数!记住了这些,我们可以编写一下能返回一个包裹函数的函数。

在函数中嵌入装饰器

我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件。

from functools import wraps
 
def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile,并写入内容
            with open(logfile, 'a') as opened_file:
                # 现在将日志打到指定的logfile
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator
 
@logit()
def myfunc1():
    pass
 
myfunc1()

myfunc1 was called

# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串
 
@logit(logfile='func2.log')
def myfunc2():
    pass
 
myfunc2()

myfunc2 was called

现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串

装饰器类

现在我们有了能用于正式环境的logit装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。比方说有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。

幸运的是,类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式,来重新构建logit。

class logit(object):

    _logfile = 'out.log'

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

    def __call__(self, *args, **kwargs):
        log_string = self.func.__name__ + " was called"
        print(log_string)
        # 打开logfile并写入
        with open(self._logfile, 'a') as opened_file:
            # Now we log to the specified logfile
            opened_file.write(log_string + '\n')
        # 现在,发送一个通知
        self.notify()

        # return base func
        return self.func(*args, **kwargs)

    def notify(self):
        # logit only logs, no more
        pass

这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:

@logit
def myfunc1():
    pass

myfunc1()

myfunc1 was called

# 现在,我们给 logit 创建子类,来添加 email 的功能(虽然 email 这个话题不会在这里展开)。
class email_logit(logit):
    '''
    一个logit的实现版本,可以在函数调用时发送email给管理员
    '''
    def __init__(self,func,email='admin@myproject.com'):
        self.email = email
        super(email_logit, self).__init__(func)

    def notify(self):
        # 发送一封email到self.email
        # 这里就不做实现了
        print("mail to: ",self.email)
        pass
@email_logit
def myfunc1():
    pass

myfunc1()

myfunc1 was called
mail to: admin@myproject.com

从现在起,@email_logit 将会和 @logit 产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。

如果我们需要装饰器类也能接收参数,那么我们可以用logit类的__init__方法来接收参数,用__call__来接收被装饰的函数:

class logit2:
    def __init__(self, outputfile='out.log'):
        self._logfile = outputfile

    def __call__(self,func):
        def wrapper(*args,**kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # Open the logfile and append
            with open(self._logfile, 'a') as opened_file:
                # Now we log to the specified logfile
                opened_file.write(log_string + '\n')
            # Now, send a notification
            self.notify()
            func(*args,**kwargs)
        # return base func
        return wrapper

    def notify(self):
        # logit only logs, no more
        pass
@logit2()
def myfunc2():
    pass

myfunc2()

myfunc2 was called

@logit2(outputfile='out3.log')
def myfunc2():
    pass

myfunc2()

myfunc2 was called

class email_logit2(logit2):
    '''
    一个logit的实现版本,可以在函数调用时发送email给管理员
    '''
    def __init__(self,outputfile='out.log',email='admin@myproject.com'):
        self.email = email
        super(email_logit2, self).__init__(outputfile)

    def notify(self):
        # 发送一封email到self.email
        # 这里就不做实现了
        print("mail to: ",self.email)
        pass
@email_logit2(outputfile='emailout.log',email='admin@admin')
def myfunc2():
    pass

myfunc2()

myfunc2 was called
mail to: admin@admin

常用装饰器

1. property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值(就是一个装饰器)

注意:被property装饰的属性会优先于对象的属性被使用,而被propery装饰的属性,分成三种:property、被装饰的函数名.setter、被装饰的函数名.deleter(都是以装饰器的形式)。

class room: #定义一个房间的类
    def __init__(self,length,width,high):
        self.length = length #房间的长
        self.width = width #房间的宽
        self.high = high #房间的高
    @property
    def area(self): #求房间的平方的功能
        return self.length * self.width #房间的面积就是:长x宽
    @property
    def perimeter(self): #求房间的周长的功能
        return 2 * (self.length + self.width) #公式为:(长 + 宽)x 2
    @property
    def volume(self): #求房间的体积的功能
        return self.length * self.width * self.high #公式为:长 x 宽 x 高

r1 = room(2,3,4) #实例化一个对象r1
print("r1.area:",r1.area) #可以像访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print("r1.perimeter:",r1.perimeter) #同上,就不用像调用绑定方法一样,还得加括号,才能运行
print("r1.volume:",r1.volume) #同上,就像是把运算过程封装到一个函数内部,我们不管过程,只要有结果就行

r1.area: 6
r1.perimeter: 10
r1.volume: 24

注意:此时的特性arear、perimeter和volume不能被赋值。

r1.area = 8 #为特性area赋值
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-119-b7f4aff58eb2> in <module>
----> 1 r1.area = 8 #为特性area赋值

AttributeError: can't set attribute

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。

class people: #定义一个人的类
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex #p1.sex = "male",遇到property,优先用property

    @property #查看sex的值
    def sex(self):
        return self.__sex #返回正真存值的地方

    @sex.setter #修改sex的值
    def sex(self,value):
        if not isinstance(value,str): #在设定值之前进行类型检查
            raise TypeError("性别必须是字符串类型") #不是str类型时,主动抛出异常
        self.__sex = value #类型正确的时候,直接修改__sex的值,这是值正真存放的地方
            #这里sex前加"__",对sex变形,隐藏。

    @sex.deleter #删除sex
    def sex(self):
        del self.__sex

p1 = people("egon","male") #实例化对象p1
print(p1.sex) #查看p1的sex,此时要注意self.sex的优先级
p1.sex = "female" #修改sex的值
print(p1.sex) #查看修改后p1的sex
print(p1.__dict__) #查看p1的名称空间,此时里面有sex
del p1.sex #删除p1的sex
print(p1.__dict__) #查看p1的名称空间,此时发现里面已经没有sex了

male
female
{‘name’: ‘egon’, ‘_people__sex’: ‘female’}
{‘name’: ‘egon’}

p1 = people("egon","male") #实例化对象p1
dir(p1)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_people__sex',
 'name',
 'sex']

此时发现多了一个私有属性_people__sex,但我们在__init__中并没用定义类似于__sex的属性,这是因为self.sex = sex调用了@sex.setter装饰的类方法。

class people: #定义一个人的类
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex #p1.sex = "male",遇到property,优先用property

    @property #查看sex的值
    def sex(self):
        return self.__sex #返回正真存值的地方

    @sex.setter #修改sex的值
    def sex(self,value):
        if not isinstance(value,str): #在设定值之前进行类型检查
            raise TypeError("性别必须是字符串类型") #不是str类型时,主动抛出异常
        print("I'm Here!!")
        self.__sex = value #类型正确的时候,直接修改__sex的值,这是值正真存放的地方
            #这里sex前加"__",对sex变形,隐藏。

    @sex.deleter #删除sex
    def sex(self):
        del self.__sex
p1 = people("egon","male")

I’m Here!!

请观察如果我们注释掉初始化方法中的self.sex = sex会发生什么:

class people: #定义一个人的类
    def __init__(self,name,sex):
        self.name = name
        # self.sex = sex #p1.sex = "male",遇到property,优先用property

    @property #查看sex的值
    def sex(self):
        print("I'm Here 2.")
        return self.__sex #返回正真存值的地方

    @sex.setter #修改sex的值
    def sex(self,value):
        if not isinstance(value,str): #在设定值之前进行类型检查
            raise TypeError("性别必须是字符串类型") #不是str类型时,主动抛出异常
        print("I'm Here!!")
        self.__sex = value #类型正确的时候,直接修改__sex的值,这是值正真存放的地方
            #这里sex前加"__",对sex变形,隐藏。

    @sex.deleter #删除sex
    def sex(self):
        print("I'm Dead.")
        del self.__sex
p1 = people("egon","male")
dir(p1)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'name',
 'sex']

显然,没有“I’m Here!!”语句被打印出来,但使用dir返回的实例p1的属性和方法列表里,还是有一个‘sex’,这是类定义中被装饰器装饰过的类方法sex,如果调用:

p1.sex
I'm Here 2.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-132-9c8b87f3cb8d> in <module>
----> 1 p1.sex

<ipython-input-128-937890e87c18> in sex(self)
      7     def sex(self):
      8         print("I'm Here 2.")
----> 9         return self.__sex #返回正真存值的地方
     10 
     11     @sex.setter #修改sex的值

AttributeError: 'people' object has no attribute '_people__sex'

显然,因为没有初始化,所以return self.__sex会显示没有这个属性。

p1.sex = 'female'

I’m Here!!

p1.sex

I’m Here 2.
‘female’

del p1.sex

I’m Dead.

2. classmethod staticmethod

另一组常用的装饰器就是@classmethod和@staticmethod。

Python 的类方法采用装饰器@classmethod来定义,我们直接看例子。

class Kls(object):
    num_inst = 0

    def __init__(self):
        Kls.num_inst = Kls.num_inst + 1
        print(self.num_inst)

    @classmethod
    def get_no_of_instance(cls):
        return cls.num_inst

ik1 = Kls()
ik2 = Kls()

1
2

print(ik1.get_no_of_instance())
print(Kls.get_no_of_instance())

2
2

在上述例子中,我们需要统计类Kls实例的个数,因此定义了一个类变量num_inst来存放实例个数。通过装饰器@classmethod的使用,方法get_no_of_instance被定义成一个类方法。在调用类方法时,Python 会将类(class Kls)传递给cls,这样在get_no_of_instance内部就可以引用类变量num_inst。
由于在调用类方法时,只需要将类型本身传递给类方法,因此,既可以通过类也可以通过实例来调用类方法。

class Kls(object):
    num_inst = 0

    def __init__(self):
        Kls.num_inst = Kls.num_inst + 1
        print(self.num_inst)

    def get_no_of_instance(cls):
        return cls.num_inst

ik1 = Kls()
ik2 = Kls()

1
2

print(ik1.get_no_of_instance())

2

print(Kls.get_no_of_instance())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-151-e65cc7087823> in <module>
----> 1 print(Kls.get_no_of_instance())

TypeError: get_no_of_instance() missing 1 required positional argument: 'cls'

如果不加类方法装饰器,那么就不会自动将类型本身传递给类方法,因此会报错。

在开发中,我们常常需要定义一些方法,这些方法跟类有关,但在实现时并不需要引用类或者实例,例如,设置环境变量,修改另一个类的变量,等。这个时候,我们可以使用静态方法。
Python 使用装饰器@staticmethod来定义一个静态方法。

IND = 'ON'

class Kls(object):
    def __init__(self, data):
        self.data = data

    @staticmethod
    def checkind():
        return IND == 'ON'

    def do_reset(self):
        if self.checkind():
            print('Reset done for: %s' % self.data)

    def set_db(self):
        if self.checkind():
            print('DB connection made for: %s' % self.data)

ik1 = Kls(24)
ik1.do_reset()
ik1.set_db()

Reset done for: 24
DB connection made for: 24

在代码中,我们定义了一个全局变量IND,由于IND跟类Kls相关,所以我们将方法checkind放置在类Kls中定义。方法checkind只需检查IND的值,而不需要引用类或者实例,因此,我们将方法checkind定义为静态方法。
对于静态方法,Python 并不需要传递类或者实例,因此,既可以使用类也可以使用实例来调用静态方法。

print(ik1.checkind())
print(Kls.checkind())

True
True

我们用代码说明实例方法,类方法,静态方法的区别。注意下述代码中方法foo,class_foo,static_foo的定义以及使用。

class Kls(object):
    def foo(self, x):
        print('executing foo(%s,%s)' % (self, x))

    @classmethod
    def class_foo(cls,x):
        print('executing class_foo(%s,%s)' % (cls,x))

    @staticmethod
    def static_foo(x):
        print('executing static_foo(%s)' % x)


ik = Kls()

# 实例方法
ik.foo(1)
print(ik.foo)
print('==========================================')

# 类方法
ik.class_foo(1)
Kls.class_foo(1)
print(ik.class_foo)
print('==========================================')

# 静态方法
ik.static_foo(1)
Kls.static_foo('hi')
print(ik.static_foo)
executing foo(<__main__.Kls object at 0x0000026B120D35F8>,1)
<bound method Kls.foo of <__main__.Kls object at 0x0000026B120D35F8>>
==========================================
executing class_foo(<class '__main__.Kls'>,1)
executing class_foo(<class '__main__.Kls'>,1)
<bound method Kls.class_foo of <class '__main__.Kls'>>
==========================================
executing static_foo(1)
executing static_foo(hi)
<function Kls.static_foo at 0x0000026B120B2BF8>

对于实例方法,调用时会把实例ik作为第一个参数传递给self参数。因此,调用ik.foo(1)时输出了实例ik的地址。

对于类方法,调用时会把类Kls作为第一个参数传递给cls参数。因此,调用ik.class_foo(1)时输出了Kls类型信息。

前面提到,可以通过类也可以通过实例来调用类方法,在上述代码中,我们再一次进行了验证。

对于静态方法,调用时并不需要传递类或者实例。其实,静态方法很像我们在类外定义的函数,只不过静态方法可以通过类或者实例来调用而已。

值得注意的是,在上述例子中,foo只是个函数,但当调用ik.foo的时候我们得到的是一个已经跟实例ik绑定的函数。调用foo时需要两个参数,但调用ik.foo时只需要一个参数。foo跟ik进行了绑定,因此,当我们打印ik.foo时,会看到以下输出:

<bound method Kls.foo of <__main__.Kls object at 0x0551E190>>

当调用ik.class_foo时,由于class_foo是类方法,因此,class_foo跟Kls进行了绑定(而不是跟ik绑定)。当我们打印ik.class_foo时,输出:

<bound method type.class_foo of <class '__main__.Kls'>>

当调用ik.static_foo时,静态方法并不会与类或者实例绑定,因此,打印ik.static_foo(或者Kls.static_foo)时输出:

<function static_foo at 0x055238B0>

概括来说,是否与类或者实例进行绑定,这就是实例方法,类方法,静态方法的区别。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒墨阁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值