尽管Python语言以其清晰的语法和易于阅读的代码风格而受到广泛赞誉,但即便是这样一门设计精良的语言,也有其独特的特性,这些特性可能会让程序员,尤其是那些刚接触Python的人,感到困惑。
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 = 10
num2 = 20
integer_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
解决方案
用于身份比较:当需要检查两个变量是否指向同一个对象时。
使用 == 进行值比较:当您要比较两个对象的内容时。