Fluent Python2 【Chapter3_QA】

1. 如何理解sys.argv

这是在test.py脚本写下的内容

import sys

print(sys.argv) # 这一行terminal中不会显示,只会在run下显示(运行结果)
# sys.argv其实就是个list, sys.argv的
打印结果为:所执行的脚本所在的绝对目录: D:/xx/xx/FluentPython2/test.py
print(sys.argv[0]) # 返回 test.py
print(sys.argv[1]) # 返回 parm1
print(sys.argv[2]) # 返回 parm2,后面若还有其他参数, 逻辑以此类推

 

这是在pycharm命令行中执行的命令

(FluentPython2Env) D:\xx\xx\FluentPython2>python test.py parm1 parm2
['test.py', 'parm1 ', 'parm2']
test.py
parm1 
parm2

 

2.正则表达式库re, 里面的group方法的理解

简单理解就是通过正则表达式提取出 括号 ( ) 里的内容,这个括号里的内容叫做捕获组

正则表达式中的 group() 方法用于从匹配的字符串中获取捕获组的值捕获组是指使用圆括号()在正则表达式模式中定义的子模式

这个方法的一般用法是: 

match_obj.group(group_index)

其中 match_objre.match()re.search() 方法的返回值,表示匹配到的对象。group_index 是要获取的捕获组的索引,默认为 0,表示整个匹配的字符串。

让我们通过一些例子来理解:

import re

# 没有捕获组
pattern = r'\d+'
match = re.match(pattern, "Number: 42")
print(match.group())     # 输出: 42

# 一个捕获组
pattern = r'(\d+)'
match = re.match(pattern, "Number: 42")
print(match.group(0))    # 输出: 42 (整个匹配)
print(match.group(1))    # 输出: 42 (第一个捕获组)

# 多个捕获组
pattern = r'(\d+)/(\d+)/(\d+)'
match = re.match(pattern, "2023/04/01")
print(match.group(0))    # 输出: 2023/04/01 (整个匹配)
print(match.group(1))    # 输出: 2023 (年份)
print(match.group(2))    # 输出: 04 (月份)
print(match.group(3))    # 输出: 01 (日期)

在上面的例子中:

  • match.group() 或 match.group(0) 返回整个匹配的字符串
  • match.group(1) 返回第一个捕获组的值。
  • match.group(2)match.group(3) 等依次返回后续捕获组的值。

捕获组在提取模式中的特定部分时特别有用,例如从一个日期字符串中提取年、月、日等信息。它们还可以用于替换操作,将匹配的子模式替换为其他内容。

总的来说,group() 方法让你可以从匹配的字符串中灵活地获取你所需的部分,使正则表达式的功能更加强大和实用。

 

3. re.compile()的理解

re.compile() 是Python中的一个内置函数,用于编译正则表达式模式,并返回一个正则表达式对象。这个对象可以被多次重复使用,从而提高性能。

在Python中使用正则表达式有两种主要方式:

  1. 直接使用正则表达式字符串
import re
pattern = r'\d+'
match = re.match(pattern, "Number: 42")

在这种情况下,每次调用 re.match()re.search() 时,都会重新编译正则表达式模式。如果你需要多次使用同一个模式,这种方式会降低效率。

  1. 使用 re.compile() 预编译正则表达式
import re
pattern = re.compile(r'\d+')
match = pattern.match("Number: 42")

通过 re.compile() 函数,Python会首先编译一次正则表达式模式,并返回一个正则表达式对象。以后每次使用这个对象时,就不需要重新编译模式了,从而提高了性能。

为什么要使用 re.compile()?

  1. 提高性能: 如果需要多次使用同一个正则表达式模式,预编译可以显著提高执行速度。
  2. 代码可读性: 将正则表达式模式封装在一个变量中,使代码更易于阅读和维护。
  3. 添加标志位: re.compile() 允许你添加额外的标志位,比如 re.IGNORECASE 用于忽略大小写匹配。

让我们看一个例子:

import re

# 不使用 re.compile()
text = "Hello, 123, world, 456"
matches = re.findall(r'\d+', text)
print(matches)  # 输出: ['123', '456']

# 使用 re.compile()
pattern = re.compile(r'\d+')
matches = pattern.findall(text)
print(matches)  # 输出: ['123', '456']

# 使用标志位
pattern = re.compile(r'[a-z]+', re.IGNORECASE)
text = "Hello, World"
matches = pattern.findall(text)
print(matches)  # 输出: ['Hello', 'World']

在上面的例子中,我们首先直接使用正则表达式字符串查找所有数字。然后,我们使用 re.compile() 预编译了一个模式对象,并使用它来执行相同的查找操作。最后,我们使用 re.IGNORECASE 标志位来忽略大小写进行匹配。

总的来说, re.compile() 可以让你预编译正则表达式模式,提高性能和可读性,并且允许你设置额外的标志位。当你需要多次使用同一个模式时,它是一个非常有用的工具。

 

4. setdefault方法的理解和使用

 

 

5. defalutdict()函数的概念、作用理解

defaultdict是Python内置的一个数据类型,它继承于dict类,是dict的一个子类它的作用是当一个key不存在于字典中时, 返回一个默认值,而不会像常规字典那样抛出KeyError异常。这可以很大程度上简化操作字典的代码。

概念:

defaultdict使用一个工厂函数来为非存在的键提供一个默认值。当我们尝试获取或设置一个不存在的键时,defaultdict会调用工厂函数来创建一个默认的实例(如列表、集合等)作为该键的值。

作用:

defaultdict常常用于处理嵌套字典(如分组字典或计数等应用场景)。使用defaultdict可以避免因为不小心尝试访问不存在的键而抛出KeyError异常,从而简化代码。

用法示例:

  1. defaultdict(int) 用于计数:
from collections import defaultdict

word_counts = defaultdict(int)
text = "hello world hello python"

for word in text.split():
    word_counts[word] += 1

print(word_counts)  # Output: defaultdict(<class 'int'>, {'hello': 2, 'world': 1, 'python': 1})

在上面的例子中,每次遇到新的单词时,defaultdict会自动创建一个值为0的键值对,这样就不会抛出KeyError异常了。

  1. defaultdict(list) 用于分组:
from collections import defaultdict

groups = defaultdict(list)
students = [
    {"name": "Alice", "grade": 9},
    {"name": "Bob", "grade": 10},
    {"name": "Charlie", "grade": 9},
    {"name": "David", "grade": 10},
]

for student in students:
    groups[student["grade"]].append(student["name"])

print(groups)  # Output: defaultdict(<class 'list'>, {9: ['Alice', 'Charlie'], 10: ['Bob', 'David']})

在上面的例子中,defaultdict(list)为每个新的年级自动创建了一个空列表,从而避免了手动初始化字典的步骤。

关于defaultdict(list):

defaultdict(list)在创建新的键时会自动为该键赋予一个新的空列表作为默认值。这在处理嵌套列表或者分组数据时非常有用。例如:

from collections import defaultdict

nested_dict = defaultdict(list)
nested_dict["colors"].append("red")
nested_dict["colors"].append("green")
nested_dict["numbers"].append(1)
nested_dict["numbers"].append(2)

print(nested_dict)
# Output: defaultdict(<class 'list'>, {'colors': ['red', 'green'], 'numbers': [1, 2]})

在上面的例子中,我们不需要事先创建colorsnumbers这两个键,defaultdict(list)会在第一次尝试访问这些键时自动创建对应的空列表,从而避免了手动初始化列表的步骤。

总之,defaultdict提供了一种简洁高效的方式来处理缺失键的情况,使代码更加整洁、易读。它在处理嵌套字典、分组数据、计数等场景中非常实用。

 

6. 关于__missing__方法的理解

 

class TestDict(dict):

    def __getitem__(self, key):
        print('call __getitem__')
        return super().__getitem__(key)

    def __missing__(self, key):
        print('call __missing__')
        raise KeyError(f'key {key} not exist')


d = TestDict()
print('set "a" to apple')
d['a'] = 'apple'
print('get "a"')
print('d["a"] -> ', d['a'])

print('get "b"')
print('d["b"] -> ', d['b'])

返回结果如下

set "a" to apple
get "a"
call __getitem__
d["a"] ->  apple
get "b"
call __getitem__
call __missing__ 
Traceback (most recent call last):
  File "D:\xx\xx\FluentPython2\test.py", line 2140, in <module>
    print('d["b"] -> ', d['b'])
  File "D:\xx\xx\FluentPython2\test.py", line 2126, in __getitem__
    return super().__getitem__(key)
  File "D:\xx\xx\FluentPython2\test.py", line 2130, in __missing__
    raise KeyError(f'key {key} not exist')
KeyError: 'key b not exist'

 

参考博客:dict.__missing__魔术方法

 

7. 如下代码中的dict(*args, **kwargs)的理解,和测试用例设计

class SimpleMappingSub(abc.Mapping):
    def __init__(self, *args, **kwargs):
        self._data = dict(*args, **kwargs)

对于 dict(*args, **kwargs) 这种构造方式,可以使用任意数量的位置参数和关键字参数来构建字典。

1) 只使用位置参数:

下面是一些示例输入数据的构造方法:

data = dict([('a', 1), ('b', 2), ('c', 3)]) # 只使用位置参数其实注意加上个 [] 就好
# 这将创建一个包含三个键值对的字典 {'a': 1, 'b': 2, 'c': 3}

2) 只使用关键字参数:

data = dict(a=1, b=2, c=3)
# 这将创建一个包含三个键值对的字典 {'a': 1, 'b': 2, 'c': 3}

3) 结合位置参数和关键字参数:

data = dict([('a', 1), ('b', 2)], c=3) 
# 这将创建一个包含三个键值对的字典 {'a': 1, 'b': 2, 'c': 3}

你可以根据函数的要求,构造适合的输入数据。

 

8. 请理解并解释下为什么如下代码会递归报错

class DictSub(dict):
    def __missing__(self, key='default'):
        return self[key]

ds = DictSub(d=4, e=5)

print(ds['f'])


Traceback (most recent call last):
  File "D:\xx\xx\FluentPython2\test.py", line 2154, in <module>
    print(ds['f'])
  File "D:\xx\xx\FluentPython2\test.py", line 2150, in __missing__
    return self[key]
  File "D:\xx\xx\FluentPython2\test.py", line 2150, in __missing__
    return self[key]
  File "D:\xx\xx\FluentPython2\test.py", line 2150, in __missing__
    return self[key]
  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded

Process finished with exit code 1

这个代码出现递归报错的原因是由于在 __missing__ 方法中,当字典中不存在某个键时,它试图通过 self[key] 的方式再次访问字典,这将再次调用 __missing__ 方法,从而产生无限递归的情况。

让我们通过以下几个步骤来详细解释:

  1. 首先,我们定义了一个名为 DictSub 的类,它继承自 dict 类。
  2. 在这个类中,我们重写了 __missing__ 方法。该方法在字典中不存在某个键时被调用。
  3. 在 __missing__ 方法中,我们返回 self[key]。这个语句试图从字典中获取值。
  4. 当我们创建一个 DictSub 的实例 ds,并尝试访问不存在的键 'f' 时,Python 会调用 __missing__ 方法。
  5. 在 __missing__ 方法中,它试图通过 self[key] 的方式获取值。但由于键 'f' 不存在,Python 会再次调用 __missing__ 方法。
  6. 这个过程会一直重复下去,导致无限递归。每次调用 __missing__ 方法时,它都会再次尝试访问不存在的键,从而再次调用 __missing__ 方法。
  7. Python 有一个最大递归深度的限制,一旦超过这个限制,它会引发 RecursionError 异常。

为了解决这个问题,我们需要在 __missing__ 方法中提供一个默认值或者采取其他合适的操作。例如,我们可以修改 __missing__ 方法如下:

class DictSub(dict):
    def __missing__(self, key):
        # 返回一个默认值或执行其他操作
        return 'Value not found'

ds = DictSub(d=4, e=5)
print(ds['f'])  # 输出: Value not found

在上面的例子中,当字典中不存在某个键时,__missing__ 方法会返回一个默认值 'Value not found',从而避免了无限递归的情况。

 

9. 为什么如下代码 绝不会被类的实例,调用__missing__方法?

class SimpleMappingSub(abc.Mapping):
    def __init__(self, *args, **kwargs):
        self._data = dict(*args, **kwargs)

    # next three methods: abstract in ABC
    def __getitem__(self, key):
        return self._data[key]

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    # never called by instances of this class
    def __missing__(self, key):
        return self[_upper(key)]

这段代码中的 __missing__ 方法永远不会被类的实例调用,主要原因是:

  1. __missing__ 方法是用于处理在字典(dict)类型中不存在的键的情况。它是Python中 dict 类的一个特殊方法。
  2. 在这个代码中, SimpleMappingSub 类继承自 abc.Mapping 抽象基类,而不是继承自 dict 类。
  3. abc.Mapping 是Python中用于定义不可变映射(Mapping)接口的抽象基类,它没有实现 __missing__ 方法。
  4. 因此,在 SimpleMappingSub 类中实现了 __getitem__ 方法来处理不存在的键,而不是使用 __missing__ 方法。当试图访问一个不存在的键时,Python会首先尝试调用 __getitem__ 方法,如果在该方法中没有处理缺失的键,则会引发 KeyError 异常。
  5. 所以,在 SimpleMappingSub 类的实例中,永远不会调用 __missing__ 方法,因为该方法是专门为 dict 类及其子类设计的。

如果你想在 SimpleMappingSub 类中处理缺失的键,你应该在 __getitem__ 方法中进行处理,而不是使用 __missing__ 方法。例如:

def __getitem__(self, key):
    try:
        return self._data[key]
    except KeyError:
        # 处理缺失的键
        return 'Default value'

在这个例子中,如果键不存在,__getitem__ 方法会捕获 KeyError 异常,并返回一个默认值。这样就可以在不引发异常的情况下处理缺失的键。

 

10. .get()和.__getitem__()方法,有何区别?.get()函数是调用的__get__() 还是__getittem__()? 【特别容易混淆】

get()__getitem__() 是两个不同的方法,它们在 Python 中具有不同的用途和行为。

  1. __getitem__(self, key) 方法:
    • 这是 Python 中的一个特殊方法,用于实现对象的下标访问操作,例如 obj[key]
    • 当您尝试访问对象的某个键时,Python 会自动调用 __getitem__ 方法。
    • 如果在 __getitem__ 方法中没有处理缺失的键,那么它会引发 KeyError 异常。
  2. get(self, key, default=None) 方法:
    • 这是 dict 类中的一个内置方法,用于获取字典中指定键对应的值。
    • 如果键存在,它会返回该键对应的值。
    • 如果键不存在,它会返回传入的默认值,而不会引发任何异常。

当您调用 obj.get(key) 时,Python 实际上是在调用 obj.__get__(key) 方法。这是因为 Python 会自动将点操作符 (.) 解释为调用对象的 __get__ 方法。

但是,在大多数情况下,__get__ 方法是由 descriptor 协议中的描述符类来实现的,而不是由普通的对象类来实现。因此,对于大多数内置类型和自定义类,调用 obj.get(key) 实际上会调用 obj.__getitem__(key) 方法。

具体来说:

  • 对于 dict 类及其子类,get 方法直接调用 __getitem__ 方法来获取值,如果键不存在,则返回默认值。
  • 对于其他类型,例如 listtuple 等,它们没有实现 get 方法,因此调用 obj.get(key) 会引发 AttributeError 异常。您应该使用 obj[key] 或 __getitem__(key) 来访问它们的元素。

总的来说,get 方法提供了一种更安全的方式来访问字典中的值,因为它不会在键不存在时引发异常。而 __getitem__ 方法则用于实现对象的下标访问操作,如果键不存在,它会引发 KeyError 异常。在处理缺失的键时,您可以选择使用哪种方式,这取决于您的具体需求。

 

11. 对于 missing.py 的最后一对例子, 为什么一个返回True, 一个返回False?

class SimpleMappingSub(abc.Mapping):
    def __init__(self, *args, **kwargs):
        self._data = dict(*args, **kwargs)

    # next three methods: abstract in ABC
    def __getitem__(self, key):
        return self._data[key]

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    # never called by instances of this class
    def __missing__(self, key):
        return self[_upper(key)]


class MappingMissingSub(SimpleMappingSub):
    def __getitem__(self, key):  # .get()方法,调用的是 __getitem__()
        try:
            return self._data[key]
        except KeyError:
            print('MappingMissingSub __getitem__ call')
            return self[_upper(key)]

class DickLikeMappingSub(SimpleMappingSub):
    def __getitem__(self, key):
        try:
            return self._data[key]
        except KeyError:
            return self[_upper(key)]

    def get(self, key, default=None):
        return self._data.get(key, default)

    def __contains__(self, key):
        return key in self._data


mms = MappingMissingSub(A='letter A')
print('a' in mms) # True

dms = DickLikeMappingSub(A='letter A')
print('a' in dms) # False

对于为什么 'a' in mms'a' in dms 返回的结果不同,主要原因在于它们是如何实现 __contains__ 方法的。

在 Python 中,当你使用 key in obj 这种形式来检查一个键是否存在于对象中时,Python 实际上是在调用对象的 __contains__ 方法。

(1) 对于 MappingMissingSub 类(mms)来说,它继承自 SimpleMappingSub类,而 SimpleMappingSub 又继承自 abc.Mapping。由于 MappingMissingSub 没有重写 __contains__ 方法,因此它使用的是 abc.Mapping 中的 __contains__ 方法的默认实现。

abc.Mapping__contains__ 方法中,它是通过调用 __getitem__ 方法来检查键是否存在的。如果 __getitem__ 方法没有引发 KeyError 异常,则认为键存在。

因此,对于 'a' in mms来说,它实际上是在调用 mms['a']。由于 MappingMissingSub 重写了 __getitem__ 方法,当键 'a' 不存在时,它会尝试获取大写键 'A' 对应的值。由于 'A' 存在于 self._data 中,因此 'a' in mms 返回 True

 

(2) 而对于 DictLikeMappingSub 类(dms)来说,   'a' in dms:

  1. DictLikeMappingSub类重写了__contains__方法。
  2. __contains__方法中,它直接检查键'a'是否在self._data字典中。
  3. 由于'a'不在self._data中,所以'a' in dms返回False

 

12. dict.fromkeys()这个方法的作用理解

dict.fromkeys(seq, value=None) 是Python内置的字典方法,它用于从序列 seq 创建一个新的字典。这个方法的作用主要有两点:

  1. 创建新字典

fromkeys 方法将 seq 中的元素作为新字典的键,使用 value 参数作为所有键对应的初始值。例如:

new_dict = dict.fromkeys(['a', 'b', 'c'], 0)
# new_dict = {'a': 0, 'b': 0, 'c': 0}
  1. 初始化字典值

通常在需要从一个序列初始化一个字典时使用,可以快速地用相同的值初始化字典中所有键对应的值。

keys = [1, 2, 3]
default_value = []
new_dict = dict.fromkeys(keys, default_value)
# new_dict = {1: [], 2: [], 3: []}

注意如果没有提供 value 参数,则所有键对应的值都将是 None

new_dict = dict.fromkeys(['a', 'b'])  
# new_dict = {'a': None, 'b': None}

另外,fromkeys 常用于从字符串中快速创建一个计数字典。

string = "hello"
count = dict.fromkeys(string, 0)
# count = {'h': 0, 'e': 0, 'l': 0, 'o': 0}

总的来说, dict.fromkeys 提供了一种快捷的方式从序列数据创建字典,并初始化字典中所有值。

 

 13. python中说调用构造函数 例如set([1,2,3]),应该如何理解

在Python中,构造函数(constructor)通常指的是__new____init__这两个特殊方法的组合。

但是,当我们调用像set([1, 2, 3])这样的写法时,我们实际上是在调用set类的__init__方法,而不是__new__方法。这是因为Python在创建对象时,会先调用__new__方法来创建对象,然后再调用__init__方法来初始化对象。

对于不可变类型(immutable types)如strinttuple等,我们几乎从不需要去重写__new__方法,因为Python会为我们优化这个过程。而对于可变类型(mutable types)如listdictset等,我们通常只需重写__init__方法就可以了。

所以,当您调用set([1, 2, 3])时,实际上Python是这样做的:

  1. 调用set.__new__(set)创建一个新的空集合对象
  2. 调用set.__init__(new_set, [1, 2, 3])来初始化这个新集合对象,将[1, 2, 3]这个列表转换为集合

因此,在大多数情况下,我们说set([1, 2, 3])是在调用构造函数,实际上是在调用__init__方法而已。除非您要自定义一个新的类,并重写__new__方法,否则通常不需要关心__new__方法的细节。

 

14. 加盐哈希概念的理解

加盐哈希(salting hashes)是一种密码存储和验证的安全实践,它通过在哈希计算中引入一个随机数据(盐值),来增加密码被暴力破解的难度。下面是对加盐哈希概念、作用和实现方式的解释:

  1. 什么是加盐哈希?

加盐哈希就是在哈希计算之前,先将密码与一个随机生成的字符串(盐值)进行拼接,再对这个组合字符串进行哈希运算。这样即使不同用户使用相同的密码,由于盐值不同,计算出的哈希值也是不同的。

  1. 加盐哈希的作用
  • 防止相同密码的哈希值相同:由于盐值的引入,使得即使相同的密码,计算出的哈希值也是不同的,从而增加攻击者破解的难度。
  • 防止预计算哈希表攻击:由于不同用户的盐值不同,攻击者无法预先计算出一个密码哈希表,再对存储的哈希值进行查表匹配。
  • 避免暴力破解时所有记录同步解密:如果不加盐,攻击者破解一个密码后,可应用于所有其他相同哈希值。加盐后,需要分别破解每个记录。
  1. 通俗解释和示例

想象一下,密码是房子的大门钥匙,哈希就像是把钥匙压扁变形,生成一个无法直接推导出钥匙的复杂形状。而加盐就相当于在钥匙上先系一个不同的钥匙扣,再将钥匙压扁。即使两个人使用了一模一样的钥匙(密码),由于钥匙扣(盐值)不同,压扁后的形状(哈希值)也不一样了。

# 不加盐哈希
pwd1 = "hello123"
pwd2 = "hello123" 
hash1 = hash(pwd1)
hash2 = hash(pwd2)
print(hash1 == hash2)  # True,相同密码的哈希值相同

# 加盐哈希
import hashlib, os

salt1 = os.urandom(32)  # 生成32字节随机盐值
salt2 = os.urandom(32)  # 生成另一个盐值
pwd1 = "hello123"
pwd2 = "hello123"

# 将盐和密码拼接后再哈希
digest1 = hashlib.pbkdf2_hmac('sha256', pwd1.encode(), salt1, 100000)  
digest2 = hashlib.pbkdf2_hmac('sha256', pwd2.encode(), salt2, 100000)

print(digest1 == digest2)  # False,盐值不同导致哈希值不同

在上述hashlib.pdkdf2_hmac这个API中,这个API hashlib.pbkdf2_hmac('sha256', pwd1.encode(), salt1, 100000)

这是Python标准库hashlib模块中的pbkdf2_hmac函数,它用于执行"基于密码的密钥derivation函数2"(Password-Based Key Derivation Function 2, PBKDF2)加盐哈希运算。

这个函数的签名是:

hashlib.pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None)

各个参数的含义是:

  1. hash_name: 指定用于PBKDF2的哈希算法,如'sha256'、'sha512'等。
  2. password: 要进行哈希的密码,通常是字节串。这里使用pwd1.encode()将字符串转为字节串。
  3. salt: 盐值,一个任意的字节串,用于加盐。这里使用os.urandom(32)生成32字节长度的随机字节串作为盐。
  4. iterations: PBKDF2算法的迭代次数,设置越大,耗时越长,但安全性也越高。这里设置为100000次。
  5. dklen: 可选参数,指定导出的密钥的长度。如果不指定,则根据所使用的哈希算法而定。

该函数的返回值是一个字节串,就是最终计算出的哈希值。

PBKDF2算法本质上是对原始密码通过大量重复哈希运算,并且每次都使用之前的哈希值再重新计算,这个过程迭代执行iterations次。同时引入了随机的salt值,使得相同密码的最终哈希值不同。

这种算法相对于简单哈希函数,耗时更长,但能极大提高暴力破解的难度。在密码学领域,PBKDF2因其设计合理而被广泛采用,成为存储密码哈希值的推荐算法。

总的来说,这段代码使用sha256哈希算法,对密码字符串pwd1加上随机盐值salt1,执行100000次PBKDF2迭代计算,最终产生一个加盐的密码哈希值。

 

14. .fromfile()  .tofile() 方法的含义是什么?

.fromfile() 方法通常用于从【文件】中读取数据并将其加载【到】【内存】中。它是许多 Python 库(如 NumPy 和 PIL 等)中常见的方法之一。

具体来说,.fromfile() 方法允许用户从文件中读取二进制数据,并将其转换为适当的数据结构,例如数组、图像等,以便在程序中进行进一步处理或分析。通常,用户可以指定数据的格式、大小、偏移量等参数,以确保正确地读取文件并解析数据。

例如,在 NumPy 库中,可以使用 .fromfile() 方法从二进制文件中读取数据并创建 NumPy 数组:

import numpy as np

# 从文件中读取数据并创建 NumPy 数组
data = np.fromfile('data.bin', dtype=np.float64)

这将从名为 'data.bin' 的文件中读取数据,并将其解释为 64 位浮点数数组。

 

.tofile() 方法通常用于将数据从【内存】中写入【到】【文件】中。它是许多 Python 库(如 NumPy 和 PIL 等)中常见的方法之一。

具体来说,.tofile() 方法允许用户将数据结构(例如数组、图像等)中的数据写入到文件中,以便将其保存到磁盘上以供后续使用。

例如,在 NumPy 库中,可以使用 .tofile() 方法将 NumPy 数组中的数据写入到二进制文件中:

import numpy as np

# 创建一个 NumPy 数组
data = np.array([1, 2, 3, 4, 5])

# 将数据写入到文件中
data.tofile('output.bin')

这将创建一个名为 'output.bin' 的二进制文件,并将数组 data 中的数据以二进制形式写入其中。

 

15. 为什么这个maxsize+1后,还是能在pycharm中打印出,并不报错呢?不是已经到了最大吗?

import sys

print(sys.maxsize)
num = 9223372036854775807 + 1
print(num)

在Python中, sys.maxsize 表示的是 Python 整数对象所能表示的最大正整数值。在64位 Python 中,这个值为 9223372036854775807

但是,Python 的整数实际上没有固定的存储大小限制,它使用了一种被称为 "无限精度整数" 的实现,可以表示任意大小的整数,只要系统内存足够。

所以,当您执行 num = 9223372036854775807 + 1 时,Python 会自动分配更多的内存来存储这个超过 sys.maxsize 的值。这就是为什么您可以在 PyCharm 中成功打印出 9223372036854775808 这个更大的整数值,而不会引发任何错误。

Python 的这种无限精度整数实现,使得它可以处理任意大小的整数,只受系统可用内存的限制。这一点不同于像 C 语言这样的语言,在 C 语言中整数有固定的存储大小,一旦超过最大值就会导致溢出错误。

所以,虽然 sys.maxsize 确实代表了 Python 整数对象的一个界限,但并不是绝对的上限,您仍然可以处理更大的整数值。这使得 Python 在进行大数运算时更加方便和灵活。

 

16. 如下代码 hashdiff.py的return表达式如何理解?

import sys

MAX_BITS = len(format(sys.maxsize, 'b'))
print(f'{MAX_BITS + 1}-bit Python build')

def hash_diff(o1, o2):
    h1 = f'{hash(o1):>0{MAX_BITS}b}'
    h2 = f'{hash(o2):>0{MAX_BITS}b}'
    diff = ''.join('!' if b1 != b2 else ' ' for b1, b2 in zip(h1, h2))
    count = f'!= {diff.count("!")}'
    width = max(len(repr(o1)), len(repr(o2)), 8)
    sep = '-' * (width * 2 + MAX_BITS)
    return (f'{o1!r:{width}} {h1}\n
        {" ":{width}} {diff} {count}\n' f'{o2!r:{width}} {h2}\n
        {sep}')
if __name__ == '__main__': print(hash_diff(1, 1.0)) print(hash_diff(1.0, 1.0001)) print(hash_diff(1.0001, 1.0002)) print(hash_diff(1.0002, 1.0003))

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2024-03-31 23:39  AlphaGeek  阅读(14)  评论(0)    收藏  举报