python魔法方法
在python中,所有以双下划线包起来的方法,都统称为"魔法方法"。比如我们接触最多的__init__。 魔法方法帮助我们定义更加符合 python 风格的对象。
1 构造和初始化
__new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法。
__init__是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。
故而“ 本质上 ”来说,__new__()方法负责创建实例,而__init__()仅仅是负责实例属性相关的初始化而已,执行顺序是,先new后init。
class Student:
def __new__(cls,*args,**kwargs):
print("我是new")
print(type(cls))#判断该类的类型,即类类型
return super().__new__(cls)
def __init__(self,stuid,stuname):
self.stuid = stuid
self.stuname = stuname
print("我是init")
def myfunc(self):
print('sssss')
s = Student(10001,'zhangsan')
print(s.stuid)
print(s.stuname)
s.myfunc()
我是new
我是init
10001
zhangsan
sssss
如果想了解有关类类型的知识,可以看下面这篇博客:
Python的类与类型
2 属性访问控制
通常情况下,我们在访问类或者实例对象的时候,会牵扯到一些属性访问的魔法方法,主要包括:
① getattr (self, name): 访问不存在的属性时调用
② getattribute (self, name):访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)
③ setattr (self, name, value):设置实例对象的一个新的属性时调用
④ delattr (self, name):删除一个实例对象的属性时调用
class Foo:
x=1
def __init__(self,y):
self.y=y
def __getattr__(self, item):
print('----> from getattr:你找的属性不存在')
def __setattr__(self, key, value):
print('----> from setattr')
# self.key=value #这就无限递归了,你好好想想
self.__dict__[key]=value #应该使用它
def __delattr__(self, item):
print('----> from delattr')
# del self.item #无限递归了
self.__dict__.pop(item)
def __getattribute__(self, item):
print('----> __getattribute__',item)
return super().__getattribute__(item)
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print()
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print()
print(f1.__dict__)
#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print()
print(f1.__dict__)
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx
----> from setattr
----> __getattribute__ __dict__----> __getattribute__ __dict__
{'y': 10}
----> from setattr
----> __getattribute__ __dict__----> __getattribute__ __dict__
{'y': 10, 'z': 3}
----> __getattribute__ __dict__
----> from delattr
----> __getattribute__ __dict__----> __getattribute__ __dict__
{'y': 10, 'z': 3}
----> __getattribute__ xxxxxx
----> from getattr:你找的属性不存在
注意:调用
self.__dict__[key]=value
会触发
def __getattribute__(self, item):
print('----> __getattribute__')
return super().__getattribute__(item)
因为 虽然是要给字典self.__dict__添加键值对,其中隐含着首先获得self.__dict__。 另外__getattribute__需要返回super().__getattribute__(item),否则函数默认返回None,报错。
3 描述符
一个实现了 描述符协议 的类就是一个描述符。 什么是描述符协议:实现了 __get__()、__set__()、__delete__() 其中至少一个方法的类,就是一个描述符。
为何要使用描述符?首先看下面这个例子
- 假想你正在给学校写一个成绩管理系统,并没有太多编码经验的你,可能会这样子写。
class Student:
def __init__(self, name, math, chinese, english):
self.name = name
self.math = math
self.chinese = chinese
self.english = english
def __repr__(self):
return "".format(
self.name, self.math, self.chinese, self.english
)
std1 = Student('小明', 76, 87, 68)
print(std1)
看起来一切都很合理,但是程序并不像人那么智能,不会自动根据使用场景判断数据的合法性,如果老师在录入成绩的时候,不小心录入了将成绩录成了负数,或者超过100,程序是无法感知的。
所以我们可以加入判断逻辑:
class Student:
def __init__(self, name, math, chinese, english):
self.name = name
if 0 <= math <= 100:
self.math = math
else:
raise ValueError("Valid value must be in [0, 100]")
if 0 <= chinese <= 100:
self.chinese = chinese
else:
raise ValueError("Valid value must be in [0, 100]")
if 0 <= chinese <= 100:
self.english = english
else:
raise ValueError("Valid value must be in [0, 100]")
def __repr__(self):
return "".format(
self.name, self.math, self.chinese, self.english
)
这时输入不符合常理的值时程序就会抛出异常,在__init__里有太多的判断逻辑,很影响代码的可读性。我们学过 property 特性,可以很好的应用在这里。于是将代码修改成如下,代码的可读性瞬间提升了不少
class Student:
def __init__(self, name, math, chinese, english):
self.name = name
self.math = math
self.chinese = chinese
self.english = english
@property
def math(self):
return self._math
@math.setter
def math(self, value):
if 0 <= value <= 100:
self._math = value
else:
raise ValueError("Valid value must be in [0, 100]")
@property
def chinese(self):
return self._chinese
@chinese.setter
def chinese(self, value):
if 0 <= value <= 100:
self._chinese = value
else:
raise ValueError("Valid value must be in [0, 100]")
@property
def english(self):
return self._english
@english.setter
def english(self, value):
if 0 <= value <= 100:
self._english = value
else:
raise ValueError("Valid value must be in [0, 100]")
def __repr__(self):
return "".format(
self.name, self.math, self.chinese, self.english
)
注意:@property和@函数名.setter中的变量必须得定义为只读(当方法名和属性名相同的时候),否则会报错,原因可以看下面这篇博客:
python中@property的使用
上例中类里的三个属性,math、chinese、english,都使用了 property 对属性的合法性进行了有效控制。功能上,没有问题,但就是太啰嗦了,三个变量的合法性逻辑都是一样的,只要大于0,小于100 就可以,代码重复率太高了,这里三个成绩还好,但假设还有地理、生物、历史、化学等十几门的成绩呢?
- 了解一下 python 的描述符。
一个实现了 描述符协议 的类就是一个描述符。
什么描述符协议:实现了 __get__()、__set__()、__delete__() 其中至少一个方法的类,就是一个描述符。- __get__: 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。
- __set__:将在属性分配操作中调用。不会返回任何内容。
- __delete__:控制删除操作。不会返回内容。
下面的Score 类是一个描述符,当从 Student 的实例访问 math、chinese、english这三个属性的时候,都会经过 Score 类里的三个特殊的方法。这里的 Score 避免了 使用property 出现大量的代码无法复用的尴尬。
class Score:
def __init__(self, default=0):
self._score = default
def __set__(self, instance, value):# python解释器自动转译为type(obj).__dict__['d'].__set__(obj, value)
if not isinstance(value, int):
raise TypeError('Score must be integer')
if not 0 <= value <= 100:
raise ValueError('Valid value must be in [0, 100]')
self._score = value
def __get__(self, instance, owner):# python解释器自动转译成type(obj).__dict__['d'].__get__(obj, type(obj))
return self._score
def __delete__(self):
del self._score
class Student:
math = Score(0)
chinese = Score(0)
english = Score(0)
def __init__(self, name, math, chinese, english):
self.name = name
self.math = math
self.chinese = chinese
self.english = english
def __repr__(self):
return "".format(
self.name, self.math, self.chinese, self.english
)
描述符应用——验证参数类型
class Typed:
def __init__(self, key, expected_type): # 构造函数接收所传入的参数和参数类型
self.key = key
self.expected_type = expected_type
def __get__(self, instance, owner):
print('get方法')
return instance.__dict__[self.key] # 从底层字典获取值
def __set__(self, instance, value):
print('set方法')
if not isinstance(value, self.expected_type): # 类型判断
raise TypeError('%s 传入的类型不是%s' % (self.key, self.expected_type)) # 格式化抛出异常
instance.__dict__[self.key] = value # 修改底层字典
def __delete__(self, instance):
print('delete方法')
instance.__dict__.pop(self.key)
class People:
name = Typed('name', str) # p1.__set__() self.__set__(),触发描述符__set__方法,设置参数类型传给构造函数
age = Typed('age', int) # p1.__set__() self.__set__()
salary = Typed('salary', float) # p1.__set__() self.__set__()
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
# p1=People('alex','13',13.3)#类型有误,报错
p1 = People('alex', 13, 13.3)
print(p1.__dict__)
print(p1.name)
p1.name = 'egon'
print(p1.__dict__)
del p1.name
print(p1.__dict__)
# print(p1.name) # 相应的键值对已在底层字典中删除了,报错
set方法
set方法
set方法
{'name': 'alex', 'age': 13, 'salary': 13.3}
get方法
alex
set方法
{'name': 'egon', 'age': 13, 'salary': 13.3}
delete方法
{'age': 13, 'salary': 13.3}
4 构造自定义容器(Container)
在python中,如果我们想实现创建类似于序列和映射的类(可以迭代以及通过[下标]返回元素),可以通过重写魔法方法__getitem__、__setitem__、__delitem__、__len__方法去模拟。
魔术方法的作用:
- __getitem__(self,key):返回键对应的值。
- __setitem__(self,key,value):设置给定键的值
- __delitem__(self,key):删除给定键对应的元素。
- __len__():返回元素的数量
'''
desc:尝试定义一种新的数据类型
等差数列
'''
class ArithemeticSequence:
def __init__(self, start=0, step=1):
print('Call function __init__')
self.start = start
self.step = step
self.myData = {}
# 定义获取值的方法
def __getitem__(self, key):
print('Call function __getitem__')
try:
return self.myData[key]
except KeyError:
return self.start + key * self.step
# 定义赋值方法
def __setitem__(self, key, value):
print('Call function __setitem__')
self.myData[key] = value
# 定义获取长度的方法
def __len__(self):
print('Call function __len__')
return len(self.myData)
# 定义删除元素的方法
def __delitem__(self, key):
print('Call function __delitem__')
del self.myData[key]
s = ArithemeticSequence(1, 2)
print(s[0])
print(s[1])
print(s[2])
print(s[3])# 这里应该执行self.start+key*self.step,因为没有3这个key
s[3] = 100 # 进行赋值
print(s[3]) # 前面进行了赋值,那么直接输出赋的值100
print(len(s))
del s[3] # 删除3这个key
Call function __init__
Call function __getitem__
1
Call function __getitem__
3
Call function __getitem__
5
Call function __getitem__
7
Call function __setitem__
Call function __getitem__
100
Call function __len__
1
Call function __delitem__
这些魔法方法的原理就是:当我们对类的属性item进行下标的操作时,首先会被__getitem__()、__setitem__()、__delitem__()拦截,从而执行我们在方法中设定的操作,如赋值,修改内容,删除内容等等。
5 上下文管理器
with 这个关键字,我们已经很熟悉了。操作文本对象的时候,我们要用 with open ,这就是一个上下文管理的例子。
with open('test.txt') as f:
print f.readlines()
- 基本语法
with EXPR as VAR:
BLOCK
-
概念
- 上下文表达式:with open('test.txt') as f:
- 上下文管理器:open('test.txt')
- f 不是上下文管理器,应该是资源对象。
如何写上下文管理器?
要自己实现这样一个上下文管理,要先知道上下文管理协议。
简单点说,就是在一个类里,如果实现了__enter__和__exit__方法,这个类的实例就是一个上下文管理器。
class Resource():
def __enter__(self):
print('===connect to resource===')
return self # 需要返回当前实例对象
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
def operate(self):
print('===in operation===')
with Resource() as res:
res.operate()
===connect to resource===
===in operation===
===close resource connection===
从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在__enter__中,而将资源的关闭写在__exit__ 中。__enter__中需要返回当前实例对象。
- 为什么要用上下文管理器?
和 python 崇尚的优雅风格有关。- 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接;
- 可以以一种更加优雅的方式,处理异常;
处理异常,通常都是使用 try...execept.. 来捕获处理的。这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。
好一点的做法呢,可以使用 with 将异常的处理隐藏起来。
仍然是以上面的代码为例,我们将1/0 这个一定会抛出异常的代码写在 operate 里
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
print(exc_type)
print(exc_val)
print(exc_tb)
return True
def operate(self):
1/0
with Resource() as res:
res.operate()
===connect to resource===
===close resource connection===
division by zero
运行一下,惊奇地发现,居然不会报错。
这就是上下文管理协议的一个强大之处,异常可以在__exit__ 进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在__exit__ 里返回 True(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。
在 写__exit__ 函数时,需要注意的事,它必须要有这三个参数:
- exc_type:异常类型
- exc_val:异常值
- exc_tb:异常的错误栈信息
当主逻辑代码没有报异常时,这三个参数将都为None。
- 理解并使用contextlib
在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。
这个点python早就想到了。它给我们提供了一个装饰器,你只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。
我们按照 contextlib 的协议来自己实现一个打开文件(with open)的上下文管理器。
import contextlib
@contextlib.contextmanager
def open_func(file_name):
# __enter__方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
# 【重点】:yield
yield file_handler
# __exit__方法
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('mydata.txt') as file_in:
for line in file_in:
print(line)
open file: mydata.txt in __enter__
ljabc
111
xixixi
hhahahah
wuwuwuw
close file: mydata.txt in __exit__
在被装饰函数里,必须是一个生成器(带有yield),而yield之前的代码,就相当于__enter__里的内容。yield 之后的代码,就相当于__exit__ 里的内容。
上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。
如果要处理异常,可以改成下面这个样子。
import contextlib
@contextlib.contextmanager
def open_func(file_name):
# __enter__方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
try:
yield file_handler
except Exception as exc:
# deal with exception
print('the exception was thrown',repr(exc))
finally:
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('mytest.txt') as file_in:
for line in file_in:
1/0
print(line)
open file: mydata.txt in __enter__
the exception was thrown ZeroDivisionError('division by zero')
close file: mydata.txt in __exit__
总结起来,使用上下文管理器有三个好处:
- 提高代码的复用率;
- 提高代码的优雅度;
- 提高代码的可读性
6 比较运算符
你想让某个类的实例支持标准的比较运算(比如>=,!=,<=,<等),但是又不想去实现那一大丢的特殊方法。
解决方案
- python类对每个比较操作都需要实现一个特殊方法来支持。 例如为了支持>=操作符,你需要定义一个 __ge__() 方法。 尽管定义一个方法没什么问题,但如果要你实现所有可能的比较方法那就有点烦人了。
- 装饰器 functools.total_ordering 就是用来简化这个处理的。 使用它来装饰一个类,你只需定义一个 __eq__() 方法, 外加其他方法(lt, le, gt, or ge)中的一个即可。 然后装饰器会自动为你填充其它比较方法。
作为例子,我们构建一些房子,然后给它们增加一些房间,最后通过房子大小来比较它们
from functools import total_ordering
class Room:
def __init__(self, name, length, width):
self.name = name
self.length = length
self.width = width
self.square_feet = self.length * self.width
@total_ordering
class House:
def __init__(self, name, style):
self.name = name
self.style = style
self.rooms = list()
@property
def living_space_footage(self):
return sum(r.square_feet for r in self.rooms)
def add_room(self, room):
self.rooms.append(room)
def __str__(self):
return '{}: {} square foot {}'.format(self.name,
self.living_space_footage,
self.style)
def __eq__(self, other):
return self.living_space_footage == other.living_space_footage
def __lt__(self, other):
return self.living_space_footage < other.living_space_footage
# Build a few houses, and add rooms to them
h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h1.add_room(Room('Office', 12, 12))
h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
h2.add_room(Room('Living Room', 18, 20))
h2.add_room(Room('Kitchen', 12, 16))
h3 = House('h3', 'Split')
h3.add_room(Room('Master Bedroom', 14, 21))
h3.add_room(Room('Living Room', 18, 20))
h3.add_room(Room('Office', 12, 16))
h3.add_room(Room('Kitchen', 15, 17))
houses = [h1, h2, h3]
print('Is h1 bigger than h2?', h1 > h2) # prints True
print('Is h2 smaller than h3?', h2 < h3) # prints True
print('Is h2 greater than or equal to h1?', h2 >= h1) # Prints False
print('Which one is biggest?', max(houses)) # Prints 'h3: 1101-square-foot Split'
print('Which is smallest?', min(houses)) # Prints 'h2: 846-square-foot Ranch'
Is h1 bigger than h2? True
Is h2 smaller than h3? True
Is h2 greater than or equal to h1? False
Which one is biggest? h3: 1101 square foot Split
Which is smallest? h2: 846 square foot Ranch
其实 total_ordering 装饰器也没那么神秘。 它就是定义了一个从每个比较支持方法到所有需要定义的其他方法的一个映射而已。 比如你定义了 __le__() 方法,那么它就被用来构建所有其他的需要定义的那些特殊方法。 实际上就是在类里面像下面这样定义了一些特殊方法:
class House:
def __eq__(self, other):
pass
def __lt__(self, other):
pass
# Methods created by @total_ordering
__le__ = lambda self, other: self < other or self == other
__gt__ = lambda self, other: not (self < other or self == other)
__ge__ = lambda self, other: not (self < other)
__ne__ = lambda self, other: not self == other
当然,你自己去写也很容易,但是使用 @total_ordering 可以简化代码,何乐而不为呢。
7 __str__和__repr__
很多时候我们自己编写一个类,在将它的实例在终端上打印或查看的时候,我们往往会看到一个不太满意的结果。
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
my_car = Car('red', 37281)
print (my_car)
my_car
<__main__.Car object at 0x00000000051AB518>
<__main__.Car at 0x51ab518>
类默认转化的字符串基本没有我们想要的一些东西,仅仅包含了类的名称以及实例的 ID (理解为 Python 对象的内存地址即可)。
所以,我们可能会手动打印对象的一些属性或者是在类里自己实现一个方法来返回我们需要的信息。
- 使用 __str__ 实现类到字符串的转化
- 不用自己另外定义一个方法,你可以在类里实现__str__ 和 __repr__方法,从而自定义类的字符串描述,这两种都是比较 Pythonic 的方式去控制对象转化为字符串的方式。
下面我们通过做实验慢慢的来看这两种方式是怎么工作的。首先,我们先加一个 __str__方法到前面的类中看看情况。
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __str__(self):
return f'a {self.color} car'
my_car = Car('red', 37281)
print (my_car)
my_car
a red car
<__main__.Car at 0x51ab278>
查看 my_car 的时候的输出仍然和之前一样,不过打印 my_car 的时候返回的内容和新加的__str__方法的返回一致。类的__str__方法会在某些需要将对象转为字符串的时候被调用。比如下面这些情况
print(my_car)
str(my_car)
'{}'.format(my_car)
a red car
'a red car'
'a red car'
有了 str 这个方法,你就不用手动去打印对象的一些信息或者添加额外的方法去达到目的。类到字符串的转化使用 str 这种 Pythonic 的方式实现即可。
- 使用 __repr__ 实现类到字符串的转化
上面我们查看 my_car 对象的时候,输出的仍是比较奇怪的结果。这是因为 Python 3 中一共有两种方式控制类到字符串的转化,第一种就是我们前面提到的 str 方法,另一个就是 repr 方法。后者的工作方式与前者类似,但是它被调用的时机不同。
这里有个简单的例子,同样是在之前的类上作改动
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __str__(self):
return '__str__ for car'
def __repr__(self):
return '__repr__ for car'
my_car = Car('red', 37281)
print(my_car)
'{}'.format(my_car)
my_car
__str__ for car
'__str__ for car'
__repr__ for car
从上面可以看出,当我们查看对象的时候(上面的最后一个操作)调用的是 __repr__ 方法。
如果我们需要显示的指定以何种方式进行类到字符串的转化,我们可以使用内置的 str() 或 repr() 方法,它们会调用类中对应的双下划线方法。
str(my_car)
repr(my_car)
'__str__ for car'
'__repr__ for car'
- __str__ 和 __repr__ 的差别
现在你可能在想,__str__和__repr__的差别究竟在哪里,它们的功能都是实现类到字符串的转化,它们的特定并没有体现出用途上的差异。
带着这个这个问题,我们试着去 Python 的标准库中找找答案。我们就来看看 datetime.date 这个类是怎么在使用这两个方法的。
import datetime
today = datetime.date.today()
today
str(today)
repr(today)
datetime.date(2020, 3, 27)
'2020-03-27'
'datetime.date(2020, 3, 27)'
因此,我们有个初步的答案。
- __str__的返回结果可读性强。也就是说,__str__的意义是得到便于人们阅读的信息,就像上面的 ‘2020-03-27’ 一样。
- __repr__的返回结果应更准确。怎么说,__repr__存在的目的在于调试,便于开发者使用。细心的读者会发现将 __repr__返回的方式直接复制到命令行上,是可以直接执行的。
但是于个人来说,如果按照通常的原则去编写代码会做很多额外的工作,两个方法的返回结果只需要对开发者友好就可以了,并不一定需要存储某个对象的完整状态。
- 每个类都最好有一个 __repr__ 方法
如果你没有添加 __str__ 方法,python 在需要该方法但找不到的时候,它会去调用 __repr__ 方法。因此,推荐在写自己的类的时候至少添加一个 __repr__ 方法,这能保证类到字符串始终有一个有效的自定义转换方式。
我们为 Car 类添加一个 __repr__ 方法
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __repr__(self):
return (f'{self.__class__.__name__}('
f'{self.color!r}, {self.mileage!r})')
def __str__(self):
return f'a {self.color} car'
my_car = Car('red', 37281)
print(my_car)
str(my_car)
my_car
repr(my_car)
a red car
'a red car'
Car('red', 37281)
"Car('red', 37281)"
注意,我们这里用了 !r 标记,是为了保证 self.color 与 self.mileage 在转化为字符串的时候使用 repr(self.color) 和 repr(self.mileage) ,而不是 str(self.color) 和 str(self.mileage) 。
- 总结
我们可以使用 __str__和 __repr__方法定义类到字符串的转化方式,而不需要手动打印某些属性或是添加额外的方法。 一般来说,__str__的返回结果在于强可读性,而 __repr__的返回结果在于准确性。 我们至少需要添加一个 __repr__方法来保证类到字符串的自定义转化的有效性,__str__是可选的。因为默认情况下,在需要却找不到 __str__方法的时候,会自动调用 __repr__方法。
8 魔法方法之__call__
在python中,函数其实是一个对象:
f = abs
print(f.__name__)
print(f(-123))
abs
123
由于 f 可以被调用,所以,f 被称为可调用对象。(所有的函数都是可调用对象)
一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法call()。
我们把 Person 类变成一个可调用对象:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print 'My name is %s...' % self.name
print 'My friend is %s...' % friend
p = Person('Bob', 'male')
p('Tim')
My name is Bob...
My friend is Tim...
单看 p('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在python中,函数也是对象,对象和函数的区别并不显著。
可以把实例对象用类似函数的形式表示,进一步模糊了函数和对象之间的概念
class Fib(object):
def __init__(self):
pass
def __call__(self,num):
a,b = 0,1;
self.l=[]
for i in range (num):
self.l.append(a)
a,b= b,a+b
return self.l
def __str__(self):
return str(self.l
f = Fib()
print(f(10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]