6个最奇怪的 Python 概念

文摘   2024-10-02 12:39   广西  

尽管Python语言以其清晰的语法和易于阅读的代码风格而受到广泛赞誉,但即便是这样一门设计精良的语言,也有其独特的特性,这些特性可能会让程序员,尤其是那些刚接触Python的人,感到困惑。

这些特性并非难以掌握,但它们确实不同于程序员在其他编程语言中的常见实践,因此显得特别引人注目。即便是那些已经积累了丰富编程经验的开发者,在面对这些概念时也可能需要调整自己的思维方式。
下面我将分享几个我个人认为在Python中最容易让人产生疑惑的概念。如果你有任何补充或不同的见解,欢迎在评论区分享你的想法。
接下来的部分我们将探讨这些可能让人感到意外的Python特性。

1.浮点运算异常

Python 很奇怪的地方之一是它处理浮点数的方式。由于底层的二进制表示法,某些计算会产生意想不到的结果。例如,由于四舍五入错误,0.1 + 0.2 可能不等于 0.3。

解决方案

使用十进制模块要进行精确的十进制运算,请导入decimal模块。

from decimal import Decimal  result = Decimal('0.1') + Decimal('0.2') print(result)  # Output: 0.3


2.可变默认参数


Python 中的函数可以使用缺省参数。但是,如果缺省参数是可变的(如 list 或 dictionary),那么在函数中进行的修改将影响后续调用。

def append_to_list(item, my_list=[]):    my_list.append(item)    return my_list
result1 = append_to_list(1)print(result1) # Output: [1]
result2 = append_to_list(2)print(result2) # Output: [1, 2]

在本例中,my_list 缺省参数是一个列表,只在函数定义时创建一次。每次调用函数时,它都会将新元素追加到同一个列表中,因此第二次调用的结果包括了第一次调用的元素

解决方案

使用不可变的默认值:避免使用可变对象作为默认参数。这会导致意想不到的行为。相反,应使用 None 作为默认参数,并在必要时创建一个新列表:

def append_to_list(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list

3.范围界定规则和非本地变量


Python 的作用域规则可能令人困惑,尤其是在处理嵌套函数和闭包的时候。非局部关键字用于修改外层但非全局作用域中的变量。

解决方案

了解作用域:了解不同的作用域(全局、本地和非本地)以及如何在其中访问变量。

谨慎使用 nonlocal变量:只有在需要修改外部作用域中的变量时,才使用 nonlocal变量。

def outside_func():    value = 10
def inside_func(): # 使用nonlocal关键字来指出我们正在修改外部作用域中的value nonlocal value value = 20 print("Inside func:", value)
inside_func() print("Outside func:", value)
outside_func()

在这个例子中,value 变量是在 outer_function 中定义的。如果不使用 nonlocal 关键字,inner_function 将创建一个名为 x 的新局部变量并修改它,而不是外部 x 变量。

4.动态类型和鸭子类型

在 Python 中,变量不会被显式地分配特定的数据类型。相反,变量的类型由它在运行时的值决定。这意味着您可以在整个代码中为同一个变量分配不同类型的值。

# 初始化变量x为整数x = 10  print("Type of x:", type(x))  # 输出:<class 'int'>
# 重新赋值x为字符串x = "Hello"print("Type of x:", type(x)) # 输出:<class 'str'>
# 再次重新赋值x为列表x = [1, 2, 3]print("Type of x:", type(x)) # 输出:<class 'list'>

鸭子类型是一种编程范式,对象的类型由其行为而非类决定。换句话说,如果一个对象叫起来像鸭子,走起路来像鸭子,那么它肯定是一只鸭子。

如果不同类的对象具有兼容的方法和属性,就可以对它们进行类似的处理,从而使代码更加灵活和动态。

def concatenate_or_add(a, b):    return a + b
# 添加两个整数num1 = 10num2 = 20integer_result = concatenate_or_add(num1, num2)print("Integer result:", integer_result) # 输出:30
# 连接两个字符串str1 = "Hello"str2 = " World" # 注意添加空格以得到期望的输出string_result = concatenate_or_add(str1, str2)print("String result:", string_result) # 输出:Hello World

解决方案

使用类型提示:虽然类型提示是可选的,但它可以提高代码的可读性和可维护性。类型提示为变量、函数和方法的预期类型提供提示。它们不会在运行时强制执行类型检查,但对静态分析工具和人工阅读者很有帮助。

def add_numbers(a: int, b: int) -> int:    return a + b

尽管 Python 的动态类型可以带来很多好处,但如果对象没有按预期使用,它也可能导致运行时出错。

5.元类和描述符

元类和描述符是高级概念,可以自定义类的行为及其属性。

元类代码示例

元类是一个可以创建其它类的类。当你定义一个类时,Python 会在幕后创建其元类的一个新实例。这个元类负责定义新类的属性和方法。

class MyMeta(type):    def __new__(cls, name, bases, attrs):        # 修改类定义        attrs['new_attribute'] = 42        return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=MyMeta): pass
print(MyClass.new_attribute)  # 输出:42

元类是Python高级主题之一,主要用于需要高度自定义类行为的情况,比如自动注册类、修改类属性或方法、动态地添加类属性等。不过,由于元类的功能强大,不当使用可能会导致代码难以理解和维护,因此应当谨慎使用。

描述符代码示例

class MyDescriptor:    def __init__(self):        self.value = None  # 初始化描述符的内部状态
def __get__(self, instance, owner): print("Getting attribute") return self.value
def __set__(self, instance, value): print("Setting attribute") self.value = value
class MyClass: my_attribute = MyDescriptor() # MyClass 中的一个描述符实例
obj = MyClass() # 创建 MyClass 的一个实例obj.my_attribute = 10 # 设置描述符的值,输出 "Setting attribute"print(obj.my_attribute) # 获取描述符的值,输出 "Getting attribute" 和 10

解决方案

对于大多数编程任务来说,元类和描述符往往不是必需的。如果需要创建自定义类行为,请仔细考虑元类或描述符是否是正确的方法。

6. is vs. ==

== 比较的是值,而 is 比较的是对象标识。在某些情况下,这会导致意想不到的结果。

例如

a = [1, 2, 3] b = a.copy() print(a == b)  # True print(a is b)  # False

解决方案

用于身份比较:当需要检查两个变量是否指向同一个对象时。

使用 == 进行值比较:当您要比较两个对象的内容时。



remote sensing
一个专注于测绘、地信、遥感的公众号
 最新文章