• python

callable 和__call__

大多数教程都告诉你int()是python的内置函数,然而当你看到int的定义,发现它竟然是类

1
2
class int(object):
pass

不只是int(),还有float(), bool(), str(),很多你以为是函数但实际上却是类,但是呢,当你使用它们时完全察觉不出它们有什么不同,本文不是要和你讨论类和函数,而是要讨论学习callable

什么是callable

一个可callable的对象是指可以被调用执行的对象,并且可以传入参数,

用另一个简单的描述方式,只要可以在一个对象的后面使用小括号来执行代码,那么这个对象就是callable对象,

下面列举callable对象的种类

  1. 函数
  2. 类里的函数
  3. 实现了__call__方法的实例对象

2.1 函数

1
2
3
4
5
def test():
print('ok')

print(callable(test)) # True
test() # ok

函数是python里的一等公民,函数是可调用对象,使用callable函数可以证明这一点

2.2 类

1
2
3
4
5
6
7
class Stu(object):
def __init__(self, name):
self.name = name


print(callable(Stu)) # True
print(Stu('小明').name) # 小明

在其他编程语言里,类与函数可以说是两个完全不搭的东西,但在python里,都是可调用对象

2.3 类里的方法

类里的方法也是用def定义的,本质上也是函数

1
2
3
4
5
6
7
8
9
10
11
12
13
from inspect import isfunction, ismethod


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

def run(self):
print('{name} is running'.format(name=self.name))

print(isfunction(Stu.run)) # True
stu = Stu("小明")
stu.run() # 小明 is running

使用isfunction函数可以判断一个对象是否是函数,run方法也是可调用对象

2.4 实现了__call__方法的实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Stu(object):

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

def __call__(self, *args, **kwargs):
self.run()

def run(self):
print('{name} is running'.format(name=self.name))

stu = Stu('小明')
print(callable(stu)) # True
stu() # 小明 is running

当你执行stu()时,与调用一个函数有着完全一致的体验,如果不告诉你stu是一个类的实例对象,你还以为stu就是一个函数。

namemain

  • “__main__” 始终指当前执行模块的名称(包含后缀.py
  • 模块被直接执行的时候,__name__ 等于字符串 “ __main__”
    如果该模块 import 到其他模块中,则该模块的 __name__ 等于模块名称(不包含后缀.py
  • print(__file__) : 文件绝对路径,c:\Users\13450\Desktop\test.py
  1. 在Python中,凡是以两个下划线开头,两个下划线结尾的变量叫做“魔法变量”。瓦特?魔法变量?对,你没有听错,就是魔法变量。当然,如果你觉得这个词不好理解的话,你可以简单地认为所谓魔法变量就是Python对象内置天生就有的属性变量,你使用这些变量前不需要自己去定义,直接用就是。当然,既然是天生就有的,你也别去修改它,正常使用就好。
  2. Python中每个py文件都叫一个模块。系统里面我们经常导入的模块,比如什么os啊,math啊,这些它们的本质都是一个个的py文件。当然,我们自己写的每个py文件也都是一个个的模块,咱们可以把它看成是一个自定义模块。模块既然就是Python文件,那么它就有两种运行方式:一种是直接运行,另外一种是导入别的模块中再运行。

if name == ‘main’:的作用

一个python文件通常有两种使用方法,

第一是作为脚本直接执行,
第二是 import 到其他的 python 脚本中被调用(模块重用)执行。

因此 if name == ‘__main__’: 的作用就是控制这两种情况执行代码的过程,在 if name == ‘__main__’: 下的代码只有在第一种情况下(即文件作为脚本直接执行)才会被执行,而 import 到其他脚本中是不会被执行的。

if name == ‘main’:的运行原理

每个python模块(python文件,也就是此处的 test.py 和 import_test.py)都包含内置的变量__name__,当该模块被直接执行的时候,__name__ 等于文件名(包含后缀 .py );如果该模块 import 到其他模块中,则该模块的 __name__ 等于模块名称(不包含后缀.py)。

而 “__main__” 始终指当前执行模块的名称(包含后缀.py)。进而当模块被直接执行时,__name__ == ‘main’ 结果为真。

当模块A被导入到模块B中时,一旦运行模块B,模块A中的语句会自动被执行一遍,以便加载模块A中的所有函数定义啊、类定义等语句到内存中等待被使用。

‘__getitem__()’

在Python中,__getitem__() 是一个特殊方法(也称为魔术方法或双下方法),它允许一个对象模拟序列或映射类型的行为,使得对象可以通过类似索引的方式访问其元素。这个方法在对象被用作索引操作时被自动调用,例如 obj[key]

当你定义一个类并想要让它支持索引操作时,你需要在类中实现 __getitem__() 方法。这个方法接受一个参数,即用作索引的键(对于序列类型通常是整数),并返回相应的值。

下面是一个简单的例子,演示了如何在自定义类中实现 __getitem__() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MySequence:
def __init__(self, data):
self.data = data

def __getitem__(self, index):
# 检查索引是否在有效范围内
if index < 0 or index >= len(self.data):
raise IndexError("Index out of range")
return self.data[index]

# 创建MySequence的实例
my_seq = MySequence([10, 20, 30, 40, 50])

# 使用索引访问元素
print(my_seq[0]) # 输出: 10
print(my_seq[2]) # 输出: 30

在这个例子中,MySequence 类有一个 data 属性,它存储了一个序列。__getitem__() 方法被定义为接受一个索引,并返回 data 列表中相应索引位置的元素。如果索引超出了范围,它会引发一个 IndexError

__getitem__() 方法还可以接受一个切片对象作为参数,这样你就可以返回一个切片视图或副本,而不是单个元素。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MySequence:
def __init__(self, data):
self.data = data

def __getitem__(self, key):
if isinstance(key, slice):
# 返回一个切片视图
return self.data[key]
else:
# 返回单个元素
return self.data[key]

# 创建MySequence的实例
my_seq = MySequence([10, 20, 30, 40, 50])

# 使用切片访问元素
print(my_seq[1:3]) # 输出: [20, 30]

在这个例子中,__getitem__() 方法检查传入的参数是否是一个切片对象。如果是,它返回序列的一个切片视图;如果不是,它返回单个元素。

通过实现 __getitem__() 方法,你可以让你的类支持索引和切片操作,从而提高类的灵活性和表达能力。

参考

  1. python 中的callable概念 - 知乎 (zhihu.com)
  2. Python中if name == ‘main‘:的作用和原理_python if-CSDN博客
  3. Python中if name == 'main’的作用是什么 - 知乎 (zhihu.com)