内建(内置)对象 (builtins)

我们常用的python内置函数,如print, help, max, dict等等是可以直接使用,不需要导入的。

__builtins__模块__builtins__ 是一个特殊的模块,它包含了所有内建(内置)的函数、异常和其他对象。当你启动一个 Python 解释器或运行一个 Python 脚本时,builtins 模块会自动加载到全局命名空间中。这意味着你可以在不显式导入的情况下直接使用这些内建函数和对象,例如 print(), len(), str(), int() 等等。

这个__builtins__就相当于我们 import builtins,不过我们没有必要显示导入builtins,因为里面这些内置对象是可以直接使用的,因为会自动加载到全局命名空间中。

print(__builtins__)
print(builtins)
print(__builtins__.print)

输出结果:

<module 'builtins' (built-in)>
<module 'builtins' (built-in)>
<built-in function print>

Stubs 文件: 当我们在使用像pycharm这种IDE去尝试查看一些内置函数的源码时,通常我们会发现导航到的源文件类似于 \user\AppData\Local\JetBrains\PyCharmCE2024.2\python_stubs\-2062853821\builtins.py 这样的路径,这是因为 PyCharm 使用了一个称为“stubs”的机制来提供更好的代码补全和类型检查功能。
Stubs 文件是一种特殊的 .pyi 文件,它们包含类型注解和其他元数据,但不包含实际的实现代码。这些文件主要用于静态分析工具(如 PyCharm 的代码编辑器),以便提供更准确的代码补全、类型检查和导航功能。
实际的内置模块和标准库模块通常是用 C 语言编写的,直接解析这些模块的源代码可能会比较慢。Stubs 文件提供了轻量级的替代方案,使得 IDE 可以更快地进行代码分析。

dir(): dir()函数用于返回某个对象的属性,其参数可以为任何对象,包括模块,类,类的实例,方法等。如果不传参数,那么dir()返回的结果取决于它被调用的范围。例如,如果其在模块全局被调用,则返回的是该模块的所有属性,如果是在函数内部,则返回的是函数内的局部变量及参数。
有时候,如果对象自己有__dir__属性,那么执行dir(obj)等价于执行obj.__dir__()。如果对象实现了自己的__dir__方法,那么dir(obj)的运行结果为__dir__方法的返回值。
传给dir的参数为类对象或实例对象时,dir返回的属性包括实例自身的属性,对应的类属性,以及类的父类的属性。
因此,dir函数的返回结果不光是看对象自己的__dict__属性,还会看其类、父类的。

print('dir在全局调用')
print(dir())

import struct
print('dir在导入模块后的全局调用')
print(dir())

def f(par):
    a = 1
    return dir()

print('dir在函数内部调用')
print(f('haha'))

class Bar():
    base_bar = 'base_bar'
    print('dir在class内部调用')
    print(dir())

class SubBar(Bar):
    sub_bar = 'sub_bar'


print('给dir传入类对象做为参数')
print(dir(Bar))

print('给dir传入子类做为参数')
print(dir(SubBar))

bar = Bar()
print('给dir传入实例对象做为参数')
print(dir(bar))

sub_bar = SubBar()
sub_bar.inst_prop = 'inst_prop'
print('给dir传入子类实例对象做为参数')
print(dir(sub_bar))

print('给dir传入函数做为参数')
print(dir(f))
print(f.__dir__())

class Foo():
    cls_pro = 'cls_pro'
    def __init__(self):
        self.obj_pro1 = 'obj_pro1'
        self.obj_pro2 = 'obj_pro2'
        self.obj_pro3 = 'obj_pro3'
    def __dir__(self):
        return [self.obj_pro1,self.obj_pro2]

foo = Foo()
print('类对象实现了__dir__方法,实例对象的dir将是')
print(dir(foo))

print(foo.__dir__())

输出结果:

dir在全局调用
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
dir在导入模块后的全局调用
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'struct']
dir在函数内部调用
['a', 'par']
dir在class内部调用
['__module__', '__qualname__', 'base_bar']
给dir传入类对象做为参数
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'base_bar']
给dir传入子类做为参数
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'base_bar', 'sub_bar']
给dir传入实例对象做为参数
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'base_bar']
给dir传入子类实例对象做为参数
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'base_bar', 'inst_prop', 'sub_bar']
给dir传入函数做为参数
['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__getstate__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__new__', '__repr__', '__call__', '__get__', '__closure__', '__doc__', '__globals__', '__module__', '__builtins__', '__code__', '__defaults__', '__kwdefaults__', '__annotations__', '__dict__', '__name__', '__qualname__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
类对象实现了__dir__方法,实例对象的dir将是
['obj_pro1', 'obj_pro2']
['obj_pro1', 'obj_pro2']

help(): help函数用于查看一个对象的说明,相当于查看对象的__doc__属性。 一般当我们不能使用像pycharm这种强大的编辑器时,可以通过help()函数在不查看源码的情况下查看该对象的使用说明。

def f():
    '''parm: 无参数
    返回一个内部变量的值'''
    a = 1
    return a
print("下面是__doc___属性输出的内容")
print('-' * 25, '分隔线', '-' * 25)
print(f.__doc__)
print()
print("下面是用help函数输出的内容")
print('-' * 25, '分隔线', '-' * 25)
print(help(f))

输出结果:

下面是__doc___属性输出的内容
------------------------- 分隔线 -------------------------
parm: 无参数
    返回一个内部变量的值

下面是用help函数输出的内容
------------------------- 分隔线 -------------------------
Help on function f in module __main__:

f()
    parm: 无参数
    返回一个内部变量的值

None

getattr(), hasattr(): getattr函数相当于obj.attribute这种方式去获取属性值,但它额外提供一个default参数,用于属性不存在时返回缺省值。否则会抛出AttributeError异常。
hasattr()用于判断一个对象的属性是否存在,因此不会抛出异常。
由于有__getattr__, __getattribute__,以及描述器的存在,python的属性访问机制是高度动态的,可以相当复杂, 而不是简单的访问对象的__dict__属性里的内容。 假设我们只考虑简单的情况,那么属性查找的顺序为 实例对象 -> 类对象 -> 类的基类对象。

class Bar():
    base_bar = 'base_bar'
    base_other_prop = 'base_other_prop'
    __class_private = 'class_private'

class SubBar(Bar):
    sub_bar = 'sub_bar'

bar = Bar()
sub_bar = SubBar()

sub_bar.self_prop = 'self_prop'
sub_bar.__private_prop = 'private_prop'

print(getattr(Bar, 'base_bar', 'Bar中base_bar不存在')) # 属性来源于类对象本身
print(getattr(bar, 'base_bar', 'bar中base_bar不存在')) # 属性来源于实例对象的类对象
print(getattr(sub_bar, 'base_bar', 'sub_bar中base_bar不存在')) # 属性来源于实例对象的类的基类
print(getattr(bar, 'self_prop', 'bar中self_prop不存在')) # 子类的实例本身的对象在父类及父类实例中是访问不到的
print(getattr(sub_bar, '__private_prop', 'sub_bar中__private_prop不存在')) # 类的实例自己添加的双下划线开头的属性可以在类的实例中直接访问到
print(getattr(sub_bar, '__class_private', 'sub_bar中__class_private不存在')) # 类对象中定义的双下划线开头的属性在类的实例中不能直接访问到
print(getattr(sub_bar, '_Bar__class_private', 'sub_bar中_Bar__class_private不存在')) # 类对象中定义的双下划线开头的属性只能以_类名__属性名的方式访问到


print('-' * 25, '分隔线', '-' * 25)

print(hasattr(Bar, 'base_bar')) # True
print(hasattr(bar, 'base_bar')) # True
print(hasattr(sub_bar, 'base_bar')) # True
print(hasattr(sub_bar, 'self_prop')) # True
print(hasattr(bar, 'self_prop')) # False  父类及实例不可能访问到子类及实例中定义的属性。
print(hasattr(sub_bar, '__private_prop')) # True 实例对象中添加的属性,即便是以双下划线开头命名,也可以直接访问。
print(hasattr(sub_bar, '__class_private')) # False 类对象中定义的双下划线开头的属性,只能以_类名__属性名 的方式访问到。
print(hasattr(sub_bar, '_Bar__class_private')) # True 类对象中定义的双下划线开头的属性,只能以_类名__属性名 的方式访问到。

print('-' * 25, '分隔线', '-' * 25)

print(sub_bar.__private_prop)  # 实例对象中添加的双下划线开头的属性可以直接访问到
print(sub_bar._Bar__class_private) # 类对象中定义的双下划线开头的私有属性,在类对象本身,及其实例对象中,只能以_类名__属性名的方式访问。

输出结果:

base_bar
base_bar
base_bar
bar中self_prop不存在
private_prop
sub_bar中__class_private不存在
class_private
------------------------- 分隔线 -------------------------
True
True
True
True
False
True
False
True
------------------------- 分隔线 -------------------------
private_prop
class_private

setattr(): setattr函数给一个对象添加一个属性,或修改一个已存在的属性值。 相当于obj.attribute=value的用法。 其也受到python属性访问机制的影响,比如描述器。 不过在简单的场景中,他的效果也与getattr与setattr这两个方法的机制有所不同。 即,当对一个对象的属性赋值时,它不会去查找其类对象及基类中的同名属性,而是直接修改当前对象的属性值(若对象中已存在同名属性),或添加一个属性(若对象自身并不存在该属性,即使其所属的类或基类中可能存在同名属性)

class Bar():
    base_bar = 'base_bar'
    base_other_prop = 'base_other_prop'
    __class_private = 'class_private'

class SubBar(Bar):
    sub_bar = 'sub_bar'

bar = Bar()
sub_bar = SubBar()

sub_bar.self_prop = 'self_prop'
sub_bar.__private_prop = 'private_prop'


print('-' * 25, '分隔线', '-' * 25)

sub_bar.base_bar = 'sub_base_bar'  # 给sub_bar实例添加自己的属性,而不是修改类对象的属性值
print(bar.base_bar) # bar实例访问的仍然是类对象的属性
print(sub_bar.base_bar) # sub_bar实例访问的是自身的属性

setattr(bar, 'base_other_prop', 'instance_other_prop')  # 给bar实例添加自己的属性,而不是修改类对象的属性值

print(bar.base_other_prop) # bar实例访问的是自身刚添加的属性
print(sub_bar.base_other_prop) # sub_bar实例由于自身没有base_other_prop属性,所以查找其类对象,发现类对象上也没有,最终在父类对象中找到了该属性。

输出结果:

------------------------- 分隔线 -------------------------
base_bar
sub_base_bar
instance_other_prop
base_other_prop

globals() 和 locals(): 这两个函数是以字典的形式分别返回全局和局部命名空间中的变量信息。 globals()函数无论用在哪里,都返回其所在模块的全局变量信息,locals()函数若用在模块全局范围,则效果等同于globlas()。修改globals()返回的字典,相当于动态修改了当前模块全局命名空间里的变量,比如增加变量,修改变量值等,一般用于元编程,不推荐普通情况下使用。但locals()返回的字典则是局部变量信息的一个快照,修改其内容并不会真正的影响到局部变量,这点是与globals()机制不同的。
dir()函数返回的是对象的变量列表,只能用于查看或检查一个对象所拥有的变量,但其列表内容与这两个函数返回的字典中的key基本上相同,并且如果locals()函数放在一个子类中,它会像dir()函数一样,返回的变量中也包含其父类中的属性。
vars(): vars函数在不传参数的情况下,用在全局范围相当于globlas函数,用在局部范围相当于locals函数。当传给其对象做为参数时,相当于访问了该对象的__dict__属性,所以不包含其类对象及父类对象中的属性。

class Parent():
    cls_parent = 'cls_parent'

class Foo(Parent):
    cls_prop = 1
    print('')
    print('-' * 25, 'inside class', '-' * 25)
    print('globals:', globals())
    print('locals:', locals())  # 变量信息里会包含其父类中的属性 cls_parent
    print('dir:', dir()) # 变量信息里会包含其父类中的属性 cls_parent
    print('vars:', vars())

def bar(par: str):
    v_local = 2
    locals()['local_bar'] = 'local_bar'  # 修改locals()返回的字典并不会真正的影响到局部变量
    vars()['local_bar2'] = 'local_bar2'  # 修改vars()返回的字典并不会真正的影响到局部变量
    print('')
    print('-' * 25, 'inside function', '-' * 25)
    print('globals:', globals())
    print('locals:', locals())
    print('dir:', dir())
    print('vars:', vars())
    try:
        print(local_bar)  # 由于并没有真正声明local_bar变量,这里会报错的。
    except NameError as e:
        print("NameError, local_bar 局部变量不存在:", e)
    try:
        print(local_bar2)  # 由于并没有真正声明local_bar2变量,这里会报错的。
    except NameError as e:
        print("NameError, local_bar2 局部变量不存在:", e)


print('')
print('-' * 25, 'outside function', '-' * 25)
print('globals:', globals())
print('locals:', locals())
print('dir:', dir())
print('vars:', vars())

bar('None') # 调用函数

globals()['haha'] = 'hehe'  # 修改globals()返回的字典会真正的影响全局变量
vars()['guagua'] = 'guagua'  # 修改vars()返回的字典会真正的影响全局变量
print('')
print('-' * 25, '动态修改globals返回的字典后', '-' * 25)
print('globals:', globals())
print('vars:', vars())
print('globals返回的字典填加的变量:', haha)  # 虽然pycharm提示这个变量不存在,但实际执行时是不报错的。
print('vars返回的字典填加的变量:', guagua)  # 虽然pycharm提示这个变量不存在,但实际执行时是不报错的。

print('')
print('-' * 25, '查看实例对象', '-' * 25)
foo = Foo()  # 实例化一个Foo类,注意其有基类。
print('dir(foo):', dir(foo))
print('vars(foo):', vars(foo))  # vars函数返回对象自身的__dict__字典里存储的变量,而不包括对应的类对象及父类对象的属性。
print('foo.__dict__:',foo.__dict__)

输出结果:

------------------------- inside class -------------------------
globals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200717F4C90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:\\RolandWork\\PythonProjects\\studyPython\\forTest.py', '__cached__': None, 'Parent': <class '__main__.Parent'>}
locals: {'__module__': '__main__', '__qualname__': 'Foo', 'cls_prop': 1}
dir: ['__module__', '__qualname__', 'cls_prop']
vars: {'__module__': '__main__', '__qualname__': 'Foo', 'cls_prop': 1}

------------------------- outside function -------------------------
globals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200717F4C90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:\\RolandWork\\PythonProjects\\studyPython\\forTest.py', '__cached__': None, 'Parent': <class '__main__.Parent'>, 'Foo': <class '__main__.Foo'>, 'bar': <function bar at 0x00000200717A04A0>}
locals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200717F4C90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:\\RolandWork\\PythonProjects\\studyPython\\forTest.py', '__cached__': None, 'Parent': <class '__main__.Parent'>, 'Foo': <class '__main__.Foo'>, 'bar': <function bar at 0x00000200717A04A0>}
dir: ['Foo', 'Parent', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'bar']
vars: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200717F4C90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:\\RolandWork\\PythonProjects\\studyPython\\forTest.py', '__cached__': None, 'Parent': <class '__main__.Parent'>, 'Foo': <class '__main__.Foo'>, 'bar': <function bar at 0x00000200717A04A0>}

------------------------- inside function -------------------------
globals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200717F4C90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:\\RolandWork\\PythonProjects\\studyPython\\forTest.py', '__cached__': None, 'Parent': <class '__main__.Parent'>, 'Foo': <class '__main__.Foo'>, 'bar': <function bar at 0x00000200717A04A0>}
locals: {'par': 'None', 'v_local': 2, 'local_bar': 'local_bar', 'local_bar2': 'local_bar2'}
dir: ['local_bar', 'local_bar2', 'par', 'v_local']
vars: {'par': 'None', 'v_local': 2, 'local_bar': 'local_bar', 'local_bar2': 'local_bar2'}
NameError, local_bar 局部变量不存在: name 'local_bar' is not defined
NameError, local_bar2 局部变量不存在: name 'local_bar2' is not defined

------------------------- 动态修改globals返回的字典后 -------------------------
globals: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200717F4C90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:\\RolandWork\\PythonProjects\\studyPython\\forTest.py', '__cached__': None, 'Parent': <class '__main__.Parent'>, 'Foo': <class '__main__.Foo'>, 'bar': <function bar at 0x00000200717A04A0>, 'haha': 'hehe', 'guagua': 'guagua'}
vars: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200717F4C90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:\\RolandWork\\PythonProjects\\studyPython\\forTest.py', '__cached__': None, 'Parent': <class '__main__.Parent'>, 'Foo': <class '__main__.Foo'>, 'bar': <function bar at 0x00000200717A04A0>, 'haha': 'hehe', 'guagua': 'guagua'}
globals返回的字典填加的变量: hehe
vars返回的字典填加的变量: guagua

------------------------- 查看实例对象 -------------------------
dir(foo): ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'cls_parent', 'cls_prop']
vars(foo): {}
foo.__dict__: {}

isinstance(): 用于判断一个对象是否是一个类或其子类(包括子类,间接子类,或虚拟子类)的实例。
先看下最简单的示例:

print(isinstance(123, int))  # True
print(isinstance(123, str))  # False

再看一下自定义类的示例:

class Animal:
    pass

class Dog(Animal):
    pass

dog = Dog()
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True

其实,可以判断一个对象是否是多个类中某一个的实例,可以使用元组,内置类型提示里的"|"符号,或使用typing.Union。

print(isinstance(123, (str, list)))  # False
print(isinstance(123, (int, list)))  # True
print(isinstance(123, str | list))  # False
print(isinstance(123, int | str))  # True

from typing import Union
print(isinstance(123, Union[str,list]))  # False
print(isinstance(123, Union[int | str]))  # True

一会补充虚拟子类的内容

issubclass(): 判断一个类是否为另一个类(或一组类)的子类(包括直接子类,间接子类,虚拟子类)。
其传参规则跟isinstance函数基本相同。

class Animal:
    pass

class Dog(Animal):
    pass

print(issubclass(Dog, Dog)) # True  判断一个类是其自身的子类也返回True
print(issubclass(Dog, Animal)) # True

print(issubclass(Dog, (int, Animal))) # True  判断一个类是其自身的子类也返回True
print(issubclass(Dog, (int, str))) # False

print(issubclass(Dog, str | list))  # False
print(issubclass(Dog, str | Animal))  # True

from typing import Union
print(issubclass(Dog, Union[str,list]))  # False
print(issubclass(Dog, Union[str | Animal]))  # True

稍后补充虚拟子类的内容

range(): 我们习惯称其为内置函数,其实range是一个内置类型,range()是其构造方法,相当于创建了一个range的实例对象。
方法签名有两种:range(stop), range(start, stop[, step])
range类实现了__iter__方法,说明其实例对象是可迭代对象(iterable),也就可以用在 item in iterable 这种表达式中。
但range不是一个生成器函数,其实例对象也不是生成器。
我们写一小段代码来验证一下:

print(hasattr(range(4), '__iter__'))   # True
print(next(range(4)))  # 报错: TypeError: 'range' object is not an iterator

因为其是可迭代对象,我们先用iter()函数来返回一个迭代器:

it = iter(range(4))  # 返回一个迭代器
print(next(it))  # 0
print(next(it))  # 1
print(next(it))  # 2   
print(next(it))  # 3

range的实例对象也支持切片和下标索引操作:

r = range(4)  # 实例化一个range对象
print(list(r))  # [0, 1, 2, 3]
print(r[2])  # 2
print(r[-1]) # 3
print(r[1:2])  # range对象的切片仍然返回range对象

range的性能: range是C语言实现的,其实是一个类,我们调用range()时,其实调用的是它的类的构造方法。成员检测的效率也远高于list,由其在长度非常大的时候

from timeit import timeit

numbers_as_range = range(10_000_000)
numbers_as_list = list(numbers_as_range)

timeit("-1 in numbers_as_range", globals=globals(), number=100)  # 4.68301093652801e-06
timeit("-1 in numbers_as_list", globals=globals(), number=100)  # 5.704572553362310

性能如此快的原因基本上类似于在其内部实现了__contains__方法,效果如下:

def __contains__(self, element)
    return self.start <= element < self.stop and (element - self.start) % self.step == 0

range对象又很节省内存,它主要记录了start, stop, step三个值,以及对其迭代时所处于哪个当前值。它用计算的方式实现了通过元素值返回index,及len()等这些list里支持的常用函数。所以当我们要使用的数据是这样有规律的数据时候,能用range尽量用range。

any(iterable): 如果可迭代对象中有任何一个数据是True,则返回True。

print(any([1, 2, 3]))               # True
print(any((False, True, False)))    # True
print(any(range(4)))                # True

但any函数只接收一个参数,否则会报错。
any(1, 2, 3) # TypeError: any() takes exactly one argument (3 given)

我们知道,python的生成器表达式是(expression for item in iterable),并且外边的括号是不能省略的,它返回一个生成器对象。所以它也可以做为any的参数。 并且,这个时候它的外层括号可以省略,这是一种语法糖。

# 下面两种写法等价
print(any((item * 2 for item in range(4))))  # True  参数为生成器表达式
print(any(item * 2 for item in range(4)))    # True  参数为生成器表达式,传给any函数时省略了括号

需要注意的是,当iterable里面内容是空时,any函数返回False。例如: print(any([])) # False
其实,any函数的等价实现如下:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

all(iterable): 如果可迭代对象中所有数据都是True,则返回True。否则返回False。
传参方式与any函数相同。 需要注意的是,当传入的iterable里面内容是空的时候,返回True。 例如 print(all([])) # True
all函数的等价实现如下:

def all(iterable):
    for element in iterable:
        if not element:
            return False
    return True

zip(): 把多个可迭代对象组合成一个可迭代对象,这个可迭代对象生成元组。方法签名:zip(*iterables, strict=False)。
strict=True时,多个可迭代对象的长度不同会报错。

print(zip([1, 2, 3], ["a", "b", "c"]))

for item in zip([1, 2, 3], ["a", "b", "c"]):
    print(item)

输出结果:

<zip object at 0x00000268F5AD4F00>
(1, 'a')
(2, 'b')
(3, 'c')

还可以只传入一个可迭代对象做为参数。
single = [1, 2, 3]
print(list(zip(single))) # [(1,), (2,), (3,)]

zip函数本质上返回一个迭代器。每次迭代都是从各个可迭代对象中选取下一个元素,组合成一个元组后返回。
print(list(zip([1, 2, 3], ['sugar', 'spice', 'everything nice'])))
输出结果: [(1, 'sugar'), (2, 'spice'), (3, 'everything nice')]

int_lst = [1, 2, 3]
str_lst = ['sugar', 'spice', 'everything nice']

for item in zip(int_lst, str_lst):
    print(item)

输出结果:

(1, 'sugar')
(2, 'spice')
(3, 'everything nice')

for循环的本质上就是在一个迭代器上不断执行next()函数,直到迭代器里元素被耗尽。所以我们可以像下面这样执行:

int_lst = [1, 2, 3]
str_lst = ['sugar', 'spice', 'everything nice']

zip_it = zip(int_lst, str_lst)  # zip函数返回一个迭代器

print(next(zip_it))
print(next(zip_it))
print(next(zip_it))

输出结果同上。
我们知道,一般情况下,迭代器本身也是可迭代对象。所以这次我们传给zip函数两个迭代器。

int_lst = [1, 2, 3]
str_lst = ['sugar', 'spice', 'everything nice']

int_it = iter(int_lst)
str_it = iter(str_lst)

for item in zip(int_it, str_it):
    print(item)

输出结果也是相同的。
应用上面的特性,我们可以把一个序列或一组数按照相同的大小(长度)拆分成多个小组。

int_lst = list(range(10))
print(int_lst)

for item in zip(*[iter(int_lst)] * 3):
    print(item)

输出结果:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(0, 1, 2)
(3, 4, 5)
(6, 7, 8)

注意,上面由zip构造的三元组其实应用的都是同一个迭代器。 下面这种写法就达不到我们的目的。因为单独运行iter(int_lst)函数会创建不同的迭代器。

int_lst = list(range(10))
print(int_lst)

for item in zip(iter(int_lst), iter(int_lst), iter(int_lst)):
    print(item)

输出结果:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(0, 0, 0)
(1, 1, 1)
(2, 2, 2)
(3, 3, 3)
(4, 4, 4)
(5, 5, 5)
(6, 6, 6)
(7, 7, 7)
(8, 8, 8)
(9, 9, 9)

你可能注意到了,原始列表中有10个数字,按三个数字分一组,应该分出4组,但最后一组应该只有一个数字。由于zip函数返回的元组个数是由最短的可迭代对象控制的,所以最后一组(it,it,it)这三个迭代器其实是同一个迭代器对象,在第一个it执行next()后就把原始数组的所有元素都耗尽了,第二个和第三个it相当于直接没有内容了,所以被认为不能组成一个完整的三元组来返回。
这种情况可以使用itertools.zip_longest函数处理。

from itertools import zip_longest

int_lst = list(range(10))
print(int_lst)

for item in zip_longest(*[iter(int_lst)] * 3, fillvalue=666):
    print(item)

输出结果:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(0, 1, 2)
(3, 4, 5)
(6, 7, 8)
(9, 666, 666)

zip_longest函数返回的元组的数量由最长的可迭代对象的长度决定。通过传递fillvalue参数来填充元组中其他位置数据的替代值。

map(): 对一个可迭代对象应用一个函数。返回一个迭代器,迭代器返回的每一个值都是函数处理后的值。

single = [1, 2, 3]
ret = map(lambda x: x**x, single)
print(ret)
for item in ret:
    print(item)

输出结果:

<map object at 0x000001AF9B1ABAC0>
1
4
27

如果传入多个iterable做为参数,则他们都将是函数的参数。

seq1 = [1, 2, 3]
seq2 = [3,2,1]
ret = map(lambda x,y: x*y, seq1, seq2)

print(list(ret))  # [3, 4, 3]

min(), max(): 内置的求一组数据中的最大值和最小值的函数。有几种函数签名形式。
下面是接收一个iterable参数的签名形式。

min(iterable, *[, default, key]) -> minimum_value
max(iterable, *[, default, key]) -> maximum_value

default和key都是可选的命名参数。
default: 用于设定当可迭代对象为空时函数返回的值。
key: 用于指定一个只有一个参数的函数,对于迭代对象中的每个元素返回一个值,也就是自定义比较的方法。
在不指定key参数的时候,使用的就是默认的比较方法。
下面示例展示了,在传入空对象时,如果没有指定default参数,就会报错。

>>> min([3, 5, 9, 1, -5])
-5

>>> min([])
Traceback (most recent call last):
    ...
ValueError: min() arg is an empty sequence

>>> max([3, 5, 9, 1, -5])
9

>>> max([])
Traceback (most recent call last):
    ...
ValueError: max() arg is an empty sequence

加了default参数,就不会报错。 min([], default='abc') # abc
下面的示例说明,可迭代对象中的数据必须是可以比较的。 但数字和字符串是不能比较的,所以会报错。

>>> min([3, "5.0", 9, 1.0, "-5"])
Traceback (most recent call last):
    ...
TypeError: '<' not supported between instances of 'str' and 'int'

>>> max([3, "5.0", 9, 1.0, "-5"])
Traceback (most recent call last):
    ...
TypeError: '>' not supported between instances of 'str' and 'int'

这时我们可能通过指定函数,来改变数据的比较方法。比如,都将其转化为float,下面这样就不会报错了。

min([3, "5.0", 9, 1.0, "-5"], key=float)  # -5
max([3, "5.0", 9, 1.0, "-5"], key=float)  # 9

下面是另一种函数签名形式,即接受多个待比较的参数(至少为两个),没有default参数,但可以有key参数。

min(arg_1, arg_2[, ..., arg_n], *[, key]) -> minimum_value
max(arg_1, arg_2[, ..., arg_n], *[, key]) -> maximum_value

使用示例如下:

>>> min(3, 5, 9, 1, -5)
-5

>>> max(3, 5, 9, 1, -5)
9

字符串也是可迭代对象,所以可以像下面这样使用:

>>> min("abcdefghijklmnopqrstuvwxyz")
'a'

>>> max("abcdefghijklmnopqrstuvwxyz")
'z'

>>> min("abcdWXYZ")
'W'

>>> max("abcdWXYZ")
'd'

注意,大写小字母的比较取决于它们的ASCII码排序。

当可迭代对象是dictionary时,直接传入dictionary时,相当于传入的是dict.keys。 函数将返回字典key的最大和最小值。

>>> prices = {
...    "banana": 1.20,
...    "pineapple": 0.89,
...    "apple": 1.57,
...    "grape": 2.45,
... }


>>> min(prices)
'apple'

>>> max(prices)
'pineapple'

还可以传入dict.items(),这样相当于传入的是一个二元组的序列。 python内部在比较元组时,会逐个比较元组中各个对应位置的元素。如果前一个元素相等,就继续比较下一个元素。

max([(0, 0), (1, 0), (1, 1), (0, 1)])  # (1, 1)
min([(0, 0), (1, 0), (1, 1), (0, 1)])  # (0, 0)
>>> min(prices.items())
('apple', 1.57)

>>> max(prices.items())
('pineapple', 2.45)

min和max函数都作用于iterable,所以当然也包括列表推导式和生成器表达式。

>>> letters = ["A", "B", "C", "X", "Y", "Z"]

>>> min(letters)
'A'
>>> min([letter.lower() for letter in letters])  # 列表推导式
'a'

>>> max(letters)
'Z'
>>> max(letter.lower() for letter in letters)  # 生成器表达式 注意,因为函数调用本身就带括号了,又因为这个示例的参数只有生成器表达式,所以可以将生成器表达式的括号省略。
'z'

自定义对象之间的比较:
min()和max()函数在内部会比较多个元素,这里说的比较其实就是调用对象内部的__lt____gt__方法。常用的数值,字符串等数据类型都已经内置实现了这两个比较方法。当python在比较x > y时,内部其实是调用了x.__gt__(y)
基于此,我们就可以做自定义对象之间的比较,请看如下示例:

from datetime import date

class Person:
    def __init__(self, name, birth_date):
        self.name = name
        self.birth_date = date.fromisoformat(birth_date)

    def __repr__(self):
        return (
            f"{type(self).__name__}"
            f"({self.name}, {self.birth_date.isoformat()})"
        )

    def __lt__(self, other):
        return self.birth_date > other.birth_date

    def __gt__(self, other):
        return self.birth_date < other.birth_date
>>> from person import Person

>>> jane = Person("Jane Doe", "2004-08-15")
>>> john = Person("John Doe", "2001-02-07")

>>> jane < john
True
>>> jane > john
False

>>> min(jane, john)
Person(Jane Doe, 2004-08-15)

>>> max(jane, john)
Person(John Doe, 2001-02-07)

另外,通常我们选择__gt__和__lt__中的一个方法就能实现>和<的比较。而不需要实现两个特殊方法。

sorted(): 内置的排序函数,接收一个iterable对象,返回一个排序后的列表。同时也提供key参数用来自定义一个函数来定制化排序规则。

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 15),
    ('dave', 'B', 10),
]
print(sorted(student_tuples, key=lambda student: student[2]))
# [('dave', 'B', 10), ('john', 'A', 15), ('jane', 'B', 15)]

上面示例用key来指定一个lambda函数来指定用年龄排序。当年龄相等时,会保留元素原来的相对顺序。

下面示例演示了对自定义类对象的排序。指定key参数对实例的属性进行排序。

class Student:
    def __init__(self, name, grade, age):
        self.name = name
        self.grade = grade
        self.age = age
    def __repr__(self):
        return repr((self.name, self.grade, self.age))

student_objects = [
    Student('john', 'A', 15),
    Student('jane', 'B', 12),
    Student('dave', 'B', 10),
]
sorted(student_objects, key=lambda student: student.age)   # sort by age
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

还可以像下面这样借助itermgetter和attrgetter来处理,据说性能更高。

from operator import itemgetter, attrgetter

class Student:
    def __init__(self, name, grade, age):
        self.name = name
        self.grade = grade
        self.age = age
    def __repr__(self):
        return repr((self.name, self.grade, self.age))

student_objects = [
    Student('john', 'A', 15),
    Student('jane', 'B', 12),
    Student('dave', 'B', 10),
]

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]

sorted(student_tuples, key=itemgetter(2))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

sorted(student_objects, key=attrgetter('age'))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

使用reverse参数进行倒序排列。

sorted(student_tuples, key=itemgetter(2), reverse=True)
# [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

sorted(student_objects, key=attrgetter('age'), reverse=True)
# [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

我们还可以通过定义类的__lt__函数,来定制化自定义对象的比较规则 。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f'Person({self.name})'

    def __lt__(self, other):
        return self.age < other.age


roland = Person('Roland', 40)
harry = Person('Harry', 12)

lst = [roland, harry]

print(sorted(lst))  # [Person(Harry), Person(Roland)]

reversed(seq): 对一个序列类型的原有顺序进行反转,返回一个迭代器。

lst = [1,3,2,5,4]
print(list(reversed(lst)))  # [4, 5, 2, 3, 1]

filter(function, iterable): python内置过滤函数,传入一个function,用于判断过滤条件,接收一个参数,返回bool类型。第二个参数是一个可迭代对象。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f'Person({self.name})'

roland = Person('Roland', 40)
harry = Person('Harry', 12)

lst = [roland, harry]

print(list(filter(lambda p: p.age > 30, lst)))  # [Person(Roland)]

当function函数返回的不是True和False时,python会根据内部规则来判断一个对象为True或False。

def identity(x):
    return x

objects = [0, 1, -2, [], 4, 5, "", None, 8]
print(list(filter(identity, objects)))  # [1, 4, 5, 8]

print(list(filter(None, objects)))  # [1, 4, 5, 8]

0值,空列表,空字符串,None等会被判定为False。同时可以注意到,这时function传入None对象是等效的。

其实filter(function, iterable)函数等价于使用生成器表达式 (item for item in iterable if function(item))。
但不等价于列表推导式,因为列表推导式返回列表。
由于filter函数是C语言实现的,所以性能优于普通的for...loop。 但生成器表达式与列表推导式同样做了内部优化,因此性能相仿。

iter(): 返回一个迭代器。有两种函数签名, iter(object), 其中object是一个可迭代对象。 判断可迭代对象的标准为,看其是否实现了一个__iter____getitem__方法。另一种函数签名是iter(object, sentinel),这时object是一个callable,sentinel是一个值,对其循环迭代时,会调用callable对象,并且不传递参数,当callable返回的值是sentinel时,迭代器终止。
下面主要展示一下第二种用法:

import random

def random_number():
    return random.randint(1, 4)

# 创建一个迭代器,当 random_number() 返回 5 时停止
my_iter = iter(random_number, 2)

for num in my_iter:
    print(num)

需要注意的是,遇到sentinel值就停止,sentinel本身并不会做为最后一次的迭代值返回。
下面示例展示了读取文件的用法,每次读取64个字节,直到读取完毕。

from functools import partial
with open('mydata.db', 'rb') as f:
    for block in iter(partial(f.read, 64), b''):
        process_block(block)

next(): 获取迭代器的下一个值。也有两种函数签名。next(iterator),这种方式当迭代器被耗尽时,会抛出StopIteration异常。 next(iterator, default),这种方式当迭代器被耗尽时不会抛出异常,而是返回default值。
下面演示一下第二种函数签名的效果:

lst = "abc"
it = iter(lst)
print(next(it, 'k'))
print(next(it, 'k'))
print(next(it, 'k'))
print(next(it, 'k'))
print(next(it, 'k'))

输出结果:

a
b
c
k
k
posted @   RolandHe  阅读(136)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示