Fluent Python2 【Chapter12_QA】

1. Jython是什么?也是一种动态编程语言吗?和python是什么关系?

答:Jython是一个在Java虚拟机(JVM)上运行的Python解释器,它允许开发者使用Python语言编写的程序在Java平台上运行。

Jython与CPython(标准的Python解释器)的不同之处在于,它将Python代码编译成Java字节码,然后在JVM上执行,而不是像CPython那样在本机机器上解释执行Python代码。

虽然Jython和Python都是用于编程的高级动态编程语言,但它们是两个不同的实现。Jython项目的目标之一是使Python代码能够与Java平台无缝集成,从而充分利用Java生态系统的强大功能和库。

Jython可以与Java代码相互调用,并且可以访问Java类库,这为开发者提供了更多的灵活性和选择。

总的来说,Jython是Python语言在Java虚拟机上的一个实现,它使得Python程序可以在Java环境中运行,并且能够与Java代码进行交互和集成。

 

2. JythonCPython解释器有何不同?

特征Jython 解释器CPython 解释器
实现语言 Java C
平台支持 能够运行在 Java 虚拟机 (JVM) 上,因此跨平台性好 主要用于 Unix、Linux、Windows 等平台
扩展模块 可以直接调用 Java 库,并且可以使用 Python 库和模块 主要使用 C 或 C++ 编写的模块
性能 通常比 CPython 解释器慢,因为在 JVM 上执行 Python 代码 通常比 Jython 解释器快,因为直接使用 C 语言实现
生态系统 与 Java 生态系统集成良好,可以利用 Java 平台的丰富资源 有着庞大的 Python 生态系统,包括大量的第三方库和工具
兼容性 支持 Python 语言的大部分特性和语法,但不支持 C 扩展模块 本地 CPython 解释器支持 Python 语言的全部特性和语法
应用领域 适用于与 Java 平台集成的项目,如企业应用、Web 开发等 适用于大多数 Python 应用,包括 Web 开发、科学计算等
编程风格 更倾向于面向对象编程和 Java 风格的编程 更倾向于 Python 的动态特性和简洁语法
社区支持 相对较小的社区,但仍有一定数量的活跃用户和贡献者 有着庞大的社区支持,包括 Python 官方和众多的第三方组织

 

选择使用哪种解释器取决于项目的需求、所需的特性以及团队的技术栈和偏好。

 

3. 为什么很多编程语言都要把代码放到虚拟机上去运行?为什么一定需要这个虚拟机,没有它会如何?C++不就没有这玩意吗?

答:将代码放到虚拟机上运行的主要原因之一是跨平台性。通过在虚拟机上运行,程序可以在不同的操作系统和硬件平台上执行,而不需要对代码进行大规模的修改。

这提高了代码的可移植性,并使得开发者可以编写一次代码,然后在多个平台上运行,而无需为每个平台编写不同的版本。

另一个原因是安全性和隔离性。虚拟机提供了一种隔离代码的机制,使得代码可以在一个受控环境中运行,而不会对主机系统造成不可预料的影响。

虚拟机可以限制代码的访问权限,防止它对系统资源进行未经授权的访问,从而提高了系统的安全性。

此外,虚拟机还可以提供一些额外的功能,如垃圾回收、动态内存分配等,这些功能可以简化开发者的工作,提高代码的可维护性和性能。

对于C++等没有虚拟机的编程语言,开发者需要自行处理跨平台兼容性和安全性等问题。虽然这些语言可以直接编译成本地机器代码,性能较高,

但在一些情况下可能需要付出更多的工作量来实现跨平台和安全性。因此,是否需要虚拟机取决于开发者的需求和项目的特点。

 

4. 引入虚拟机的优点和缺点 分别有哪些呢?

引入虚拟机的优点包括:

  1. 跨平台性:虚拟机可以在不同的操作系统和硬件平台上运行相同的代码,提高了代码的可移植性。
  2. 安全性:虚拟机提供了隔离环境,可以限制代码的访问权限,防止其对系统造成损害。
  3. 简化开发:虚拟机可以提供一些额外的功能,如垃圾回收、动态内存分配等,简化了开发者的工作,提高了代码的可维护性和性能。
  4. 灵活性:虚拟机可以动态加载和卸载代码,灵活地管理系统资源,提高了系统的灵活性和可扩展性。

然而,引入虚拟机也有一些缺点

  1. 性能损失:虚拟机在执行代码时会引入一定的性能开销,包括解释和执行字节码、垃圾回收等。这可能会导致程序的性能略低于直接在本地机器上运行的情况。
  2. 资源占用:虚拟机需要占用一定的内存和处理器资源来运行,这可能会增加系统的负担。
  3. 学习成本:使用虚拟机开发和调试代码可能需要一些额外的学习成本,因为开发者需要了解虚拟机的工作原理和相关的调试技术。
  4. 安全漏洞:虚拟机本身也可能存在安全漏洞,导致恶意代码可以利用这些漏洞对系统进行攻击。因此,虚拟机的安全性也需要被认真对待。

总的来说,引入虚拟机可以提高代码的可移植性、安全性和灵活性,但也需要权衡其性能、资源占用和学习成本等方面的因素。

 

5. 如下是从from array import array 中截取的源码,这些不同的typecode分别表示什么含义?

_IntTypeCode = Literal["b", "B", "h", "H", "i", "I", "l", "L", "q", "Q"]
_FloatTypeCode = Literal["f", "d"]
_UnicodeTypeCode = Literal["u"]
_TypeCode = Union[_IntTypeCode, _FloatTypeCode, _UnicodeTypeCode]

答:from array import array 这行代码导入了 Python 的 array 模块,并且从中导入了 array 类。这个类用于表示一维数组,并提供了在数组中存储和操作数据的功能。

下面是 typecode 的各个含义:

  1. "b":有符号字符(signed char)
  2. "B":无符号字符(unsigned char)
  3. "h":有符号短整型(signed short)
  4. "H":无符号短整型(unsigned short)
  5. "i":有符号整型(signed int)
  6. "I":无符号整型(unsigned int)
  7. "l":有符号长整型(signed long)
  8. "L":无符号长整型(unsigned long)
  9. "q":有符号长长整型(signed long long)
  10. "Q":无符号长长整型(unsigned long long)
  11. "f":单精度浮点数(float)
  12. "d":双精度浮点数(double)
  13. "u":Unicode 字符(unicode)

这些 typecode 分别代表了数组中存储的数据类型。在使用 array 类创建数组时,可以指定其中存储的数据类型,即选择合适的 typecode

 

6. python里面为什么有了单精度浮点 还要搞个双精度浮点?为什么不统一就做一种浮点类型就行了?

答:Python中有单精度浮点和双精度浮点的概念,主要是为了满足不同场景下对精度和内存占用的需求。虽然 Python 的内建类型 float 一般对应双精度浮点数,

但在某些情况下,可能需要使用单精度浮点数来节省内存或者进行特定的数值计算。因此,Python提供了一些库来处理单精度浮点数,如 numpy 中的 float32 类型。

以下是单精度浮点和双精度浮点的区别以及为什么不统一使用一种浮点类型的原因:

  1. 精度差异:双精度浮点数(double)提供更高的精度,通常使用64位来表示,而单精度浮点数(float)只有32位,因此其精度相对较低。在一些对精度要求较高的科学计算或工程领域,双精度浮点数更为常见。

  2. 内存占用:双精度浮点数占用的内存空间更大,因为它需要存储更多的位数以提供更高的精度。在内存有限的环境下,可能会选择单精度浮点数来节省内存。

  3. 计算效率:双精度浮点数的计算通常比单精度浮点数更耗时,因为它需要处理更多的位数。在一些对计算效率要求较高的应用中,可能会选择单精度浮点数来提高计算速度。

在如下例子中,我们使用了 NumPy 库中的 np.float32np.float64 函数来明确指定浮点数的类型。这样可以确保两个示例中的浮点数类型是不同的,并且可以查看它们在内存中的占用空间。

import numpy as np

# 使用单精度浮点数
float32_number = np.float32(3.14159265358979323846)
print("Single Precision Float:", float32_number)
print("Memory Usage (float32):", float32_number.nbytes, "bytes")

# 使用双精度浮点数
float64_number = np.float64(3.14159265358979323846)
print("Double Precision Float:", float64_number)
print("Memory Usage (float64):", float64_number.nbytes, "bytes")

# 返回结果
Single Precision Float: 3.1415927
Memory Usage (float32): 4 bytes
Double Precision Float: 3.141592653589793
Memory Usage (float64): 8 bytes

综上所述,Python提供了单精度浮点和双精度浮点的选择,以满足不同场景下的需求。选择合适的浮点类型取决于具体的应用场景,需要综合考虑精度、内存占用和计算效率等因素。

 

7. @overload这个装饰器的概念、作用、必要性和有无对比举例说明。

答:@overload 装饰器是 Python 的 functools 模块中的一个工具,用于实现函数重载。函数重载指的是在同一个作用域内定义多个同名函数,但这些函数具有不同的参数类型和/或参数个数。

Python 原生不支持函数重载,但可以使用 @overload 装饰器模拟实现。

以下是 @overload 装饰器的概念、作用、必要性和有无对比的说明:

  • 概念:@overload 装饰器用于定义函数重载,允许在同一个作用域内定义多个同名函数,并根据不同的参数类型和/或参数个数进行匹配和调用。

  • 作用:主要作用是增加代码的可读性和灵活性,使函数能够根据传入的参数类型和/或参数个数执行不同的逻辑。

  • 必要性:在某些情况下,特定函数需要处理多种类型或数量的参数,此时使用函数重载可以简化代码,并使代码更易于理解和维护。

  • 有无对比举例说明:没有 @overload 装饰器时,通常需要使用条件语句或者默认参数值来处理不同情况下的逻辑。而使用了 @overload 装饰器之后,可以直接在函数上方定义多个同名函数,根据参数类型和/或个数自动匹配调用,使代码更加清晰和简洁。

下面是一个简单的例子,演示了如何使用 @overload 装饰器定义重载函数:

from typing import overload

@overload
def process_data(data: int) -> str:
    return f"Processing integer data: {data}"

@overload
def process_data(data: str) -> str:
    return f"Processing string data: {data}"

def process_data(data):
    if isinstance(data, int):
        return f"Processing integer data: {data}"
    elif isinstance(data, str):
        return f"Processing string data: {data}"
    else:
        raise ValueError("Unsupported data type")

print(process_data(10))    # Output: Processing integer data: 10
print(process_data("abc")) # Output: Processing string data: abc

 

8. python中常见的可迭代对象有哪些?

  1. 列表(List)
  2. 元组(Tuple)
  3. 字符串(String)
  4. 集合(Set)
  5. 字典(Dictionary)
  6. 文件对象(File Object)
  7. range() 函数返回的范围对象
  8. enumerate() 函数返回的枚举对象
  9. zip() 函数返回的压缩对象
  10. filter() 函数返回的过滤器对象
  11. map() 函数返回的映射对象
  12. 所有实现了迭代器协议的自定义对象
# 列表
my_list = [1, 2, 3, 4, 5]

# 元组
my_tuple = (1, 2, 3, 4, 5)

# 字符串
my_string = "Hello, world!"

# 集合
my_set = {1, 2, 3, 4, 5}

# 字典
my_dict = {'a': 1, 'b': 2, 'c': 3}

# 文件对象
file_object = open("example.txt", "r")

# 范围对象
my_range = range(5)

# 枚举对象
my_enum = enumerate(my_list)

# 压缩对象
my_zip = zip(my_list, my_tuple)

# 自定义对象
class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        value = self.data[self.index]
        self.index += 1
        return value

my_custom_object = MyIterator([1, 2, 3, 4, 5])

 

9.为什么要先 tuple再str()?

class Vector:
    def __str__(self):
        return str(tuple(self))

首先当不加tuple()的时候

举例来说明:在__str__方法中直接调用str(self)可能会导致无限递归的问题。

假设我们有这样一个自定义的类:

class MyClass:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return str(self)

obj = MyClass(42)
print(str(obj))

在这个例子中,我们定义了一个名为MyClass的类,它有一个__str__方法,该方法直接返回str(self)

当我们创建一个MyClass的实例obj,并尝试打印它的字符串表示形式str(obj)时,会发生以下情况:

  1. str(obj)会调用obj对象的__str__方法。
  2. __str__方法中,返回str(self)
  3. str(self)又会调用self对象的__str__方法,即再次调用同一个__str__方法。
  4. 这个过程会一直无限循环下去,直到达到最大递归深度,导致程序崩溃并抛出RecursionError异常。

输出可能如下:

RecursionError: maximum recursion depth exceeded while calling a Python object

为了避免这种无限递归的情况,我们可以使用str(tuple(self))来返回Vector对象的字符串表示形式:

class Vector:
    def __init__(self, *components):
        self.components = components

    def __str__(self):
        return str(tuple(self.components))  # 返回 str(tuple(self.components))

v = Vector(1, 2, 3)
print(str(v))  # 输出: (1, 2, 3)

 

追问:为什么str(tuple(self.components))不会再次调用__str__方法,而是直接返回一个字符串。

在Python中,当你调用str(obj)时,它会自动调用obj对象的__str__方法来获取对象的字符串表示形式。如果在__str__方法中又调用了str(self)str(obj)等同样会触发__str__方法的语句,就会导致无限递归。

然而,当你调用str(tuple(self.components)),它会做以下操作:

  1. tuple(self.components)会创建一个新的元组对象,其中包含self.components中的所有元素。
  2. 然后,str(tuple(...))会直接获取这个新创建的元组对象的字符串表示形式,而不会调用任何__str__方法

换句话说,str(tuple(...))是Python内置的一种直接将元组转换为字符串的方式,它不会触发任何自定义的__str__方法。

因此,当__str__方法返回str(tuple(self.components))时,它不会再次调用自身,从而避免了无限递归的问题。

相比之下,如果__str__方法直接返回str(self)或者类似的会触发__str__方法的语句,就会导致无限递归,因为它会一直调用自身。

所以,使用str(tuple(self.components))的主要目的是避免无限递归,同时也提供了一种标准的方式来表示对象的内部结构。这种

 

综上:

在 Vector 类的 __str__ 方法中,先将对象转换为元组 tuple(self),然后再将元组转换为字符串 str(tuple(self)),主要有以下几个原因:

  1. 防止无限递归

如果直接在 __str__ 方法中调用 str(self),可能会导致无限递归。因为 str(self) 会再次调用 __str__ 方法,从而形成无限循环。而将对象先转换为元组,就可以避免这种情况发生。

  1. 提供一致的字符串表示

将对象转换为元组后,再将元组转换为字符串,可以确保对象的字符串表示形式具有一致性。无论对象是什么类型,都会先转换为元组,然后再转换为字符串。这样可以提供一种标准的字符串表示形式。

  1. 适用于任意可迭代对象

将对象转换为元组后,再转换为字符串,这种方式不仅适用于自定义类的对象,也适用于任意可迭代对象。因为 tuple(self) 会将任何实现了 __iter__ 方法的对象转换为元组。

  1. 展示内部结构

通过将对象转换为元组,再转换为字符串,可以方便地展示对象的内部结构。元组的字符串表示形式正是展示对象内部组成元素的一种简单方式。

 

10. __match_args__的概念的理解

__match_args__ 是从 Python 3.10 开始引入的一个新特性,它用于定义对象的结构化模式匹配语法。

概念: __match_args__ 是一个类属性,它指定了在使用结构化模式匹配语法时,应该匹配对象的哪些属性。它接受一个元组或可迭代对象,其中包含要匹配的属性名称。

作用:

  • 它使结构化模式匹配更加方便和直观,无需访问对象的内部属性。
  • 它提供了一种标准化的方式来定义对象的匹配模式,从而增强了代码的可读性和可维护性。
  • 它为对象提供了一种显式的匹配语义,而不是依赖于隐式的行为。

通俗解释: 假设你有一个表示银行账户的对象,包含账户持有人的姓名、账号和余额等信息。如果你想根据账户持有人的姓名来判断是否是某个特定的账户,你可以使用结构化模式匹配。

通过定义 __match_args__,你可以指定在进行模式匹配时,应该关注账户持有人的姓名属性。这样,你就可以方便地匹配和识别特定的账户对象。

 

我们通过一个例子来对比有无 __match_args__ 的情况,以加深对这个概念的理解。

首先,我们定义一个 BankAccount 类,不包含 __match_args__ 属性:

class BankAccount:
    def __init__(self, owner, account_number, balance):
        self.owner = owner
        self.account_number = account_number
        self.balance = balance

account1 = BankAccount('Alice', '123456', 1000)
account2 = BankAccount('Bob', '789012', 2000)

如果我们想在模式匹配中匹配账户持有人和账号,由于缺少 __match_args__,我们需要访问对象的内部结构。一种方法是使用元组解包:

def check_account(account):
    match account:
        case BankAccount(owner, account_number, _):
            if owner == 'Alice':
                print("This is Alice's account.")
            elif owner == 'Bob':
                print("This is Bob's account.")
            else:
                print("Unknown account.")
        case _:
            print("Invalid account.")

check_account(account1)  # 输出: This is Alice's account.
check_account(account2)  # 输出: This is Bob's account.

在这个例子中,我们在模式匹配中使用 BankAccount(owner, account_number, _) 来解包对象的属性。

然后,我们根据 owner 的值来判断是否是 Alice 或 Bob 的账户。

另一种方法是直接访问对象的属性:

def check_account(account):
    match account:
        case account if account.owner == 'Alice':
            print("This is Alice's account.")
        case account if account.owner == 'Bob':
            print("This is Bob's account.")
        case _:
            print("Unknown account.")

check_account(account1)  # 输出: This is Alice's account.
check_account(account2)  # 输出: This is Bob's account.

在这个例子中,我们使用 account.owner 来直接访问对象的 owner 属性,然后根据它的值进行模式匹配。

相比之下,如果我们在 BankAccount 类中定义了 __match_args__ 属性:

class BankAccount:
    __match_args__ = ('owner', 'account_number')

    def __init__(self, owner, account_number, balance):
        self.owner = owner
        self.account_number = account_number
        self.balance = balance

account1 = BankAccount('Alice', '123456', 1000)
account2 = BankAccount('Bob', '789012', 2000)

def check_account(account):
    match account:
        case BankAccount('Alice', _):  # 这样写起来就比较简洁,没有那么多if-else
            print("This is Alice's account.")
        case BankAccount('Bob', _):
            print("This is Bob's account.")
        case _:
            print("Unknown account.")

check_account(account1)  # 输出: This is Alice's account.
check_account(account2)  # 输出: This is Bob's account.

我们可以直接在模式匹配中使用 BankAccount('Alice', _)BankAccount('Bob', _) 来匹配对象的 owneraccount_number 属性,而无需访问对象的内部结构。这样可以使代码更加简洁和易读。

通过这个对比,我们可以看到:

  • 如果没有 __match_args__,我们需要使用元组解包或直接访问对象的属性来进行模式匹配,这使得代码更加冗长和难以理解。
  • 有了 __match_args__,我们可以直接在模式匹配中指定要匹配的属性,这使得代码更加简洁和直观,也更容易维护。

__match_args__ 为对象提供了一种显式定义匹配语义的方式,而不需要依赖对象的内部结构。这提高了代码的可读性和可维护性,也使得模式匹配更加标准化和一致。

 

11. __slots__的概念理解

概念 __slots__是Python中一个内置的类属性,它定义了该类实例所允许拥有的属性集合。__slots__属性的值应该是一个字符串的可迭代对象,其中每个字符串代表一个实例属性的名称。

作用

  • 内存优化:使用__slots__可以减少实例所需的内存量,因为Python不再为实例分配字典来存储实例属性。
  • 防止意外创建实例属性:__slots__限制了实例只能拥有预先定义的属性集合,防止意外地创建新的实例属性。
  • 提高属性访问速度:由于不需要使用字典来存储实例属性,因此访问属性的速度会更快。
  • 减小序列化对象的大小:在将实例序列化为JSON或其他格式时,只有__slots__中定义的属性会被序列化,从而减小了序列化对象的大小。

通俗解释 __slots__就像是一把钥匙,它决定了一个锁头(类实例)只能插入哪些钥匙孔(属性)。如果你试图插入一把未经批准的钥匙(新属性),这把钥匙就会被拒绝。

通过__slots__的限制,我们可以确保每个锁头都只能插入预先批准的钥匙,从而防止意外插入错误的钥匙,并且也可以减少锁头本身所需要的空间。

使用举例

class MyClass:
    __slots__ = ('x', 'y')

obj = MyClass()
obj.x = 1   # 合法,x在__slots__中
obj.y = 2   # 合法,y在__slots__中

obj.z = 3   # 非法,z不在__slots__中,会引发AttributeError异常

在这个例子中,我们定义了一个MyClass,它只允许拥有xy两个实例属性。

当我们尝试为实例obj设置一个不在__slots__中的z属性时,Python会引发一个AttributeError异常。

我们还可以通过__slots__来节省内存:

import sys

class MyClass:
    __slots__ = ()   # 不允许任何实例属性

class OldClass:
    pass             # 允许动态添加任意实例属性

obj1 = MyClass()
obj2 = OldClass()

print(sys.getsizeof(obj1))  # 输出: 28
print(sys.getsizeof(obj2))  # 输出: 40

在这个例子中,我们定义了一个没有实例属性的MyClass和一个允许动态添加实例属性的OldClass

由于MyClass没有使用字典来存储实例属性,因此它占用的内存空间比OldClass要小。

需要注意的是,虽然__slots__可以带来诸多好处,但它也有一些限制。

 

例如,如果类定义了__slots__属性,那么该类的实例就无法再拥有自定义的__dict__属性(该属性用于存储实例属性)。

此外,如果你想要在__slots__中添加新的实例属性,你需要重新定义整个类。

总之,__slots__是一个非常有用的功能,它可以优化内存使用、防止意外创建实例属性、提高属性访问速度等。

但同时也要注意它的局限性,在使用时需要权衡利弊。

 

12. 如下三种计算异或的方式,当数据量大的时候,计算性能优劣的排名是多少

n = 0
for i in range(1, 6):
    n ^= i
    
import functools
functools.reduce(lambda a, b: a^b, range(6))

import operator
functools.reduce(operator.xor, range(6))

我用如下代码实际本机测试后:

from timeit import timeit

setup = "data = range(10**5); " \
        "import functools; " \
        "import operator"

stmt1 = """
n = 0
for i in data:
    n ^= i
"""

stmt2 = "functools.reduce(lambda a, b: a^b, data)"
stmt3 = "functools.reduce(operator.xor, data)"

print("Loop:", timeit(stmt1, setup=setup, number=1000))
print("Lambda:", timeit(stmt2, setup=setup, number=1000))
print("Operator:", timeit(stmt3, setup=setup, number=1000))

Loop: 8.596307499996328  
Lambda: 17.64684120000311
Operator: 7.245174499999848

 

 

 

 

 

 

 

 

posted @ 2024-04-11 15:20  AlphaGeek  阅读(40)  评论(0)    收藏  举报