ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python面试核心:从基础语法到内存管理与高级特性深度解析

Python面试核心:从基础语法到内存管理与高级特性深度解析 1. 从面试官视角看Python基础题的价值最近帮团队面试了几轮初级和中级Python开发发现一个挺有意思的现象很多候选人简历上项目写得天花乱坠框架、中间件、高并发张口就来但一碰到最基础的Python语言特性题回答就开始含糊其辞甚至直接卡壳。比如问“Python里is和有什么区别”能清晰说出“is比较的是对象标识符id比较的是值”的十个里大概只有三四个。再追问一句“那什么情况下a is b为True但a b可能为False”能举出小整数缓存或字符串驻留例子的就更少了。这让我意识到面试官手里那些所谓的“经典基础题”其考察目的远不止于检验你是否背过答案。它们更像是一面镜子能清晰地照出一个开发者对这门语言底层机制的理解深度、编码时的思考习惯以及解决实际问题时的严谨性。Python以其语法简洁著称但“简洁”不等于“简单”。恰恰是这些看似基础的语法糖和内置机制背后隐藏着内存管理、对象模型、作用域规则等核心概念。能把基础题讲透的人往往在写业务代码时更少出“妖蛾子”debug时思路也更清晰。因此我整理了这份“Python基础篇面试题集锦”。它不仅仅是一个问题列表更融合了我作为面试官的出题思路、作为过来人的踩坑经验以及希望候选人能具备的底层认知。我会围绕每个问题不仅给出“标准答案”更会深入剖析“为什么这么问”、“常见的错误理解是什么”、“在实际编码中如何应用和避坑”。希望它能帮你真正吃透Python基础在面试中展现出扎实的内功。2. 变量、对象与内存理解Python的“一切皆对象”这是所有Python面试的起点也是区分“会用Python”和“理解Python”的第一道分水岭。很多开发者写了很久代码但对变量、对象、引用这三者的关系依然模糊。2.1 可变对象与不可变对象一切行为的根源Python中所有数据都是对象每个对象都有三个基本属性标识id内存地址、类型type和值value。根据对象的值能否被改变分为可变对象如list, dict, set和不可变对象如int, float, str, tuple。核心陷阱赋值不是拷贝而是贴标签。a [1, 2, 3] b a # b只是a的一个新标签指向同一个列表对象 b.append(4) print(a) # 输出[1, 2, 3, 4]a也被修改了这里的b a并没有创建新的列表只是让变量b也指向了变量a所指向的那个列表对象。所以通过b修改列表a看到的自然也是被修改后的内容。这是初学者在函数传参时最容易踩的坑。那么如何真正复制一个列表呢浅拷贝b a.copy()或b a[:]或b list(a)。这创建了一个新的列表对象但新列表中的元素如果它们是可变对象依然和原列表共享。a [[1, 2], 3] b a.copy() b[0].append(99) print(a) # 输出[[1, 2, 99], 3] 内部列表被修改了深拷贝import copy; b copy.deepcopy(a)。这会递归地拷贝所有嵌套的可变对象创建完全独立的副本。面试官追问场景“请问下面代码的输出是什么并解释原因。”def func(d): d[a] 10 d {b: 20} data {} func(data) print(data)很多候选人会答{b: 20}。正确答案是{a: 10}。原因在于函数调用时实参data将其引用指向空字典的标签传递给了形参d。所以d[a] 10修改的是data和d共同指向的那个字典。随后d {b: 20}让形参d指向了一个全新的字典对象但这丝毫不会影响实参data仍然指向原来的字典。这个例子完美诠释了“传对象引用”而非“传值”或“传引用”的概念。2.2is与身份与相等的哲学这是必考题但很多人只背了结论没理解其应用场景和背后的优化机制。is比较两个变量的内存地址是否相同即id(a) id(b)。判断的是“是不是同一个对象”。调用对象的__eq__()方法比较两个对象的值是否相等。判断的是“值看起来是否一样”。关键考点小整数池与字符串驻留Python出于性能考虑对一定范围内的小整数通常是-5到256和简单的字符串进行了缓存驻留。这意味着这些对象在内存中只有一份。a 100 b 100 print(a is b) # True因为100在小整数池内是同一个对象 c 1000 d 1000 print(c is d) # 在CPython中通常为False除非在同一个代码块中经编译器优化。但 c d 永远为True。 s1 hello s2 hello print(s1 is s2) # 通常为True字符串驻留机制重要提示永远不要用is来比较数值或字符串是否相等应该始终使用。is只用于比较单例对象如None。if x is None: # 正确 if x None: # 功能相同但风格不佳 if x is 100: # 错误虽然可能碰巧对但行为不确定。2.3 垃圾回收引用计数与循环引用Python通过引用计数为主标记-清除和分代回收为辅的机制自动管理内存。引用计数每个对象都有一个计数器记录有多少个引用指向它。当引用计数归零时对象立即被销毁。这是最直接、快速的回收方式。import sys a [] print(sys.getrefcount(a)) # 输出可能是2a变量 getrefcount的形参 b a print(sys.getrefcount(a)) # 输出变为3 del b print(sys.getrefcount(a)) # 输出变回2sys.getrefcount()本身调用也会增加一个临时引用所以看到的数字会比预期多1。循环引用问题引用计数的致命弱点。当两个或多个对象相互引用形成环它们的引用计数永远不会归零。class Node: def __init__(self): self.parent None self.children [] node1 Node() node2 Node() node1.children.append(node2) node2.parent node1 # 此时即使删除外部变量node1和node2的引用计数仍为1相互引用无法被引用计数回收。 del node1, node2标记-清除为了解决循环引用Python的垃圾回收器会定期执行。它从一组“根对象”如当前栈帧中的变量、全局变量等出发标记所有可达的对象。然后清扫堆内存释放那些未被标记的对象即不可达的循环引用组。分代回收基于“年轻对象很快死老对象存活久”的假设。新创建的对象在第0代经历一次垃圾回收后存活下来的移到第1代以此类推。回收器会更频繁地检查年轻代而对老年代检查较少以此平衡回收开销。面试实战技巧当被问到“Python的垃圾回收机制”时不要只背“引用计数”。一定要提到它的局限性循环引用和补充机制标记-清除、分代回收并最好能举一个循环引用的例子。这能立刻体现你的深度。3. 函数进阶参数、作用域与装饰器函数是Python的一等公民对其机制的考察是面试的重中之重。3.1 参数传递的“花式”玩法Python的函数参数非常灵活但也是混淆的重灾区。位置参数、关键字参数、默认参数是最基础的。关键要理解默认参数只计算一次这个坑。def append_to(element, target[]): # 默认参数target[]在函数定义时就被计算并创建 target.append(element) return target print(append_to(1)) # 输出[1] print(append_to(2)) # 输出[1, 2] surprise!第二次调用时target使用的是第一次调用后已经被修改过的那个默认列表。正确做法是使用None作为默认值def append_to(element, targetNone): if target is None: target [] target.append(element) return target*args和**kwargs是可变参数。*args接收任意数量的位置参数在函数内部作为一个元组。**kwargs接收任意数量的关键字参数在函数内部作为一个字典。 它们可以同时使用但顺序必须是(普通参数, *args, **kwargs)。def logger(func): def wrapper(*args, **kwargs): # 这样可以接受func的任何签名 print(fCalling {func.__name__}) return func(*args, **kwargs) # 在这里原样传回 return wrapper这里wrapper函数用*args, **kwargs接收所有参数在调用原始函数func时又用*args, **kwargs“解包”传递出去从而实现了对任意函数的透明包装。这是装饰器的经典写法。仅限关键字参数在*args之后或一个单独的*之后出现的参数必须通过关键字指定。def connect(host, port, *, timeout10, sslFalse): pass connect(localhost, 8080, timeout5) # 正确 connect(localhost, 8080, 5) # 错误timeout必须用关键字这个特性在需要强制提高代码可读性或避免参数顺序错误时非常有用。3.2 作用域与闭包变量查找的“LEGB”规则当在函数内部访问一个变量时Python会按照LLocal局部- EEnclosing闭包- GGlobal全局- BBuilt-in内置的顺序进行查找。闭包是引用了外部函数变量的内部函数。它“记住”了它被定义时的环境。def make_multiplier(factor): def multiplier(x): return x * factor # 这里引用了外部函数的变量factor return multiplier double make_multiplier(2) triple make_multiplier(3) print(double(5)) # 输出10 print(triple(5)) # 输出15double和triple是两个不同的闭包它们各自“记住”了不同的factor值2和3。闭包是实现装饰器、函数工厂和回调机制的基础。global和nonlocal关键字用于在内部作用域中修改外部变量。global声明变量来自全局作用域。nonlocal声明变量来自最近的嵌套作用域非全局。count 0 def outer(): num 10 def inner(): nonlocal num # 引用outer函数的num global count # 引用全局的count num 1 count 1 inner() print(num, count) # 输出11 1如果不使用nonlocalinner函数中的num 1会被认为是创建一个新的局部变量num从而导致UnboundLocalError。3.3 装饰器不修改源代码的“魔法”装饰器本质上是一个接受函数作为参数并返回一个新函数的高阶函数。它提供了强大的AOP面向切面编程能力。编写一个记录函数运行时间的装饰器import time import functools def timer(func): functools.wraps(func) # 重要保留原函数的元信息如__name__ def wrapper(*args, **kwargs): start_time time.perf_counter() result func(*args, **kwargs) end_time time.perf_counter() print(f{func.__name__} took {end_time - start_time:.4f} seconds) return result return wrapper timer def slow_function(): time.sleep(1) slow_function() # 输出slow_function took 1.0012 seconds print(slow_function.__name__) # 输出slow_function 如果没有functools.wraps会输出wrapperfunctools.wraps(func)是一个非常重要的最佳实践它将被装饰函数的__name__、__doc__等属性复制到包装函数中使得装饰器对使用者更加透明。带参数的装饰器这需要再嵌套一层函数。def repeat(num_times): 装饰器工厂返回一个装饰器 def decorator_repeat(func): functools.wraps(func) def wrapper(*args, **kwargs): for _ in range(num_times): result func(*args, **kwargs) return result # 通常返回最后一次调用的结果 return wrapper return decorator_repeat repeat(num_times3) def greet(name): print(fHello {name}) greet(World) # 输出 # Hello World # Hello World # Hello World这里repeat(num_times3)实际上先调用了repeat(3)它返回decorator_repeat这个真正的装饰器然后这个装饰器再应用到greet函数上。理解这个“三层嵌套”结构是掌握高级装饰器的关键。类装饰器如果一个类实现了__call__方法它也可以作为装饰器使用。class CountCalls: def __init__(self, func): functools.update_wrapper(self, func) self.func func self.num_calls 0 def __call__(self, *args, **kwargs): self.num_calls 1 print(fCall {self.num_calls} of {self.func.__name__}) return self.func(*args, **kwargs) CountCalls def say_hello(): print(Hello!) say_hello() # 输出Call 1 of say_hello \n Hello! say_hello() # 输出Call 2 of say_hello \n Hello! print(say_hello.num_calls) # 输出2类装饰器的好处是可以更方便地维护状态如这里的调用次数num_calls。4. 面向对象编程类与元类的深度Python的OOP有其独特之处深入理解其模型能让你写出更优雅、强大的代码。4.1 实例属性、类属性与方法解析顺序实例属性 vs 类属性实例属性定义在__init__或实例方法中以self.为前缀每个实例独享。类属性定义在类体中所有实例共享。修改类属性会影响所有实例这是一个常见的坑。class Dog: tricks [] # 类属性所有Dog实例共享 def __init__(self, name): self.name name def add_trick(self, trick): self.tricks.append(trick) # 这里修改的是类属性 d1 Dog(Fido) d2 Dog(Buddy) d1.add_trick(roll over) d2.add_trick(play dead) print(d1.tricks) # 输出[roll over, play dead] 两个狗的trick混在一起了正确的做法是将tricks作为实例属性在__init__中初始化self.tricks []。方法解析顺序MRO与super() 在多重继承中Python使用C3线性化算法来确定方法查找顺序可以通过ClassName.__mro__查看。class A: def say(self): print(A) class B(A): def say(self): print(B) super().say() # 调用MRO中的下一个类的say方法 class C(A): def say(self): print(C) super().say() class D(B, C): def say(self): print(D) super().say() print(D.__mro__) # 输出(class __main__.D, class __main__.B, class __main__.C, class __main__.A, class object) d D() d.say() # 输出 # D # B # C # Asuper()并不是简单地调用父类而是按照__mro__列表顺序调用“下一个”类。这使得协作式多重继承成为可能。在B.say中super().say()调用的是C.say而不是A.say因为MRO顺序是D - B - C - A。4.2 魔法方法让对象“活”起来魔法方法双下划线方法是Python丰富对象行为的核心。__new__和__init____new__是一个静态方法负责创建对象分配内存并返回这个新对象。__init__是一个实例方法负责初始化对象设置初始状态。__new__在__init__之前被调用。class Singleton: _instance None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance super().__new__(cls) # 调用object的__new__创建实例 return cls._instance # 永远返回同一个实例 def __init__(self, value): self.value value # 注意每次初始化都会覆盖value a Singleton(1) b Singleton(2) print(a is b) # True print(a.value, b.value) # 输出2 2 因为第二次__init__覆盖了value单例模式这个例子揭示了__new__和__init__的关系。__init__在每次实例化时都会被调用即使返回的是同一个对象。因此在单例模式中要小心__init__重复初始化带来的副作用。__str__和__repr____str__面向用户追求可读性__repr__面向开发者追求明确性理想情况下eval(repr(obj)) obj。class Point: def __init__(self, x, y): self.x x self.y y def __repr__(self): return fPoint({self.x}, {self.y}) # 明确的、可复现的表示 def __str__(self): return f({self.x}, {self.y}) # 简洁的、可读的表示 p Point(1, 2) print(repr(p)) # 输出Point(1, 2) print(str(p)) # 输出(1, 2) print(p) # 默认调用 __str__输出(1, 2)__getitem__、__setitem__、__len__让自定义类拥有序列或映射的行为。class MyList: def __init__(self, data): self._data list(data) def __getitem__(self, index): print(fGetting item at {index}) return self._data[index] def __setitem__(self, index, value): print(fSetting item at {index} to {value}) self._data[index] value def __len__(self): return len(self._data) ml MyList([1, 2, 3]) print(ml[1]) # 输出Getting item at 1 \n 2 ml[1] 99 # 输出Setting item at 1 to 99 print(len(ml)) # 输出3__call__让实例可以像函数一样被调用。class Adder: def __init__(self, n): self.n n def __call__(self, x): return self.n x add5 Adder(5) print(add5(10)) # 输出15 print(callable(add5)) # 输出True这常用于创建有状态的函数或实现装饰器类。4.3 元类类的类元类是Python中最“黑魔法”的概念之一。元类是类的类它控制类的创建行为。type是所有类的默认元类。使用type动态创建类# 传统方式 class MyClass: x 10 # 动态创建方式 MyClass type(MyClass, (), {x: 10})type(name, bases, dict)三个参数类名、继承的父类元组、类的属性字典。自定义元类通过继承type并重写__new__或__init__方法。class Meta(type): def __new__(cls, name, bases, dct): # 在类创建前可以修改属性字典 dct[created_by_meta] True return super().__new__(cls, name, bases, dct) def __init__(self, name, bases, dct): super().__init__(name, bases, dct) # 类创建后可以做一些事情 self._registry [] class MyClass(metaclassMeta): pass print(MyClass.created_by_meta) # 输出True print(MyClass._registry) # 输出[]元类的典型应用场景包括ORM框架如Django的Model、API验证、自动注册子类、强制编码规范等。例如一个简单的注册元类class PluginRegistry(type): _plugins [] def __new__(cls, name, bases, dct): new_class super().__new__(cls, name, bases, dct) if name ! BasePlugin: # 避免注册基类本身 cls._plugins.append(new_class) return new_class class BasePlugin(metaclassPluginRegistry): pass class PluginA(BasePlugin): pass class PluginB(BasePlugin): pass print(PluginRegistry._plugins) # 输出[class __main__.PluginA, class __main__.PluginB]所有继承BasePlugin的类都会被自动注册到PluginRegistry._plugins列表中无需手动添加。这展示了元类如何介入类的创建过程实现框架级别的控制。5. 迭代器、生成器与协程惰性求值的艺术Python通过迭代器协议支持对容器元素的惰性访问生成器则将其发挥到极致而协程在此基础上实现了更复杂的控制流。5.1 迭代器协议__iter__与__next__一个对象如果实现了__iter__()方法返回一个迭代器对象或__getitem__()方法支持从0开始的整数索引它就是可迭代的Iterable。for循环就是基于此。迭代器Iterator则是一个实现了__iter__()返回自身和__next__()方法的对象。__next__()每次返回下一个元素没有更多元素时抛出StopIteration异常。class CountDown: def __init__(self, start): self.current start def __iter__(self): return self # 迭代器必须返回自身 def __next__(self): if self.current 0: raise StopIteration num self.current self.current - 1 return num for num in CountDown(3): print(num) # 输出3 2 1for循环的内部机制大致相当于iterator iter(CountDown(3)) # 调用 __iter__ while True: try: num next(iterator) # 调用 __next__ print(num) except StopIteration: break5.2 生成器优雅的惰性序列生成器是一种特殊的迭代器它通过yield关键字定义在每次迭代时“产出”一个值并暂停执行下次迭代时从暂停处继续。生成器函数def fibonacci(limit): a, b 0, 1 while a limit: yield a # 产出当前值并在此暂停 a, b b, a b gen fibonacci(10) # 调用函数返回一个生成器对象代码并未执行 print(next(gen)) # 输出0 执行到 yield a 暂停 print(next(gen)) # 输出1 从 a, b b, ab 开始执行到 yield a 暂停 print(list(gen)) # 输出[2, 3, 5, 8] 继续迭代直到结束生成器函数在调用时并不执行函数体而是返回一个生成器对象。每次调用next()函数执行到yield语句返回其后的值并暂停。再次调用next()从上次暂停处继续。生成器表达式更简洁的语法类似于列表推导式但用圆括号。squares_gen (x*x for x in range(10)) # 生成器表达式惰性计算 squares_list [x*x for x in range(10)] # 列表推导式立即计算所有值 print(next(squares_gen)) # 输出0 print(sum(squares_gen)) # 输出285 从1*1加到9*9因为0已被取出生成器表达式在处理大规模数据时能节省大量内存因为它一次只产生一个值。send()、throw()与close()生成器的高级通信与控制。send(value)向生成器发送一个值该值将成为当前yield表达式的结果并返回生成器产出的下一个值。throw(type[, value[, traceback]])在生成器暂停的yield处抛出一个异常。close()在生成器暂停处抛出GeneratorExit异常终止生成器。def accumulator(): total 0 while True: value yield total # yield total并等待send进来的值赋给value if value is None: break total value acc accumulator() next(acc) # 启动生成器执行到 yield total 暂停返回0 print(acc.send(10)) # 发送10value10total10执行到 yield total 暂停返回10 print(acc.send(20)) # 发送20total30返回30 acc.close() # 终止生成器这为生成器赋予了双向通信的能力是理解协程的基础。5.3 从生成器到协程async/await的基石协程Coroutine可以理解为更通用的、可多次进入和退出的子程序。在Python中基于生成器的协程是asyncio库早期的基础。生成器作为协程def simple_coroutine(): print(- coroutine started) x yield # 第一次send必须为None或使用next()启动产出None print(f- coroutine received: {x}) y yield x 10 print(f- coroutine received: {y}) coro simple_coroutine() next(coro) # 或 coro.send(None) 输出- coroutine started 执行到第一个yield暂停 result coro.send(20) # 发送20给x打印执行到第二个yield暂停产出 x1030 print(result) # 输出30 coro.send(100) # 发送100给y打印协程结束抛出StopIterationyield关键字在这里既用于产出值也用于接收外部发送的值。这种模式使得生成器可以暂停、接收输入、继续执行具备了协程的雏形。yield from用于简化在生成器中委托给另一个生成器的操作也是早期实现异步IO的关键。def chain(*iterables): for it in iterables: yield from it # 等价于 for item in it: yield item list(chain(AB, range(3))) # 输出[A, B, 0, 1, 2]yield from subgen会建立调用方和子生成器subgen的直接通道调用方可以直接send()值给subgensubgen产出的值也直接返回给调用方。这为复杂的生成器嵌套和协程委托提供了清晰的语法。现代Python中原生的协程通过async def定义使用await来挂起。但其核心思想——在等待IO时挂起并让出控制权——与生成器协程一脉相承。理解生成器的这些高级特性对于深入理解asyncio的底层机制大有裨益。6. 异常处理、上下文管理与描述符健壮的程序离不开优雅的错误处理和资源管理Python提供了强大的工具来实现这些。6.1 异常处理不仅仅是try...except自定义异常继承自Exception类。class ValidationError(Exception): 自定义验证异常 def __init__(self, message, field): super().__init__(message) self.field field # 附加额外的上下文信息 def validate_age(age): if age 0: raise ValidationError(Age cannot be negative, age) if age 150: raise ValidationError(Age seems unrealistic, age) try: validate_age(-5) except ValidationError as e: print(fError in field {e.field}: {e})自定义异常可以携带更多关于错误上下文的信息让上层处理逻辑更清晰。else和finally子句else当try块中没有异常抛出时执行。finally无论是否发生异常最终都会执行。常用于清理资源如关闭文件、释放锁。def process_file(filename): f None try: f open(filename, r) data f.read() except FileNotFoundError: print(fFile {filename} not found) return else: print(fSuccessfully read {len(data)} bytes) return data finally: if f: f.close() # 确保文件总是被关闭 print(Cleanup done)else子句将正常逻辑与异常处理分离使代码结构更清晰。finally是资源安全的最后保障。异常链使用raise ... from ...保留原始异常信息。def inner(): raise ValueError(Inner error) def outer(): try: inner() except ValueError as e: raise RuntimeError(Outer error) from e try: outer() except RuntimeError as e: print(e) # 输出Outer error print(e.__cause__) # 输出ValueError: Inner error这在封装底层异常为更高级别的业务异常时非常有用调试时能追溯到根本原因。6.2 上下文管理器与with语句上下文管理器用于在进入和退出某个代码块时执行特定的设置和清理操作最典型的例子是文件操作with open(...) as f:。实现上下文管理器一个类需要实现__enter__和__exit__方法。class ManagedFile: def __init__(self, filename, mode): self.filename filename self.mode mode self.file None def __enter__(self): print(fOpening file {self.filename}) self.file open(self.filename, self.mode) return self.file # as 子句得到的对象 def __exit__(self, exc_type, exc_val, exc_tb): print(fClosing file {self.filename}) if self.file: self.file.close() # 如果返回True则with块中的异常会被抑制否则异常会继续传播 return False with ManagedFile(test.txt, w) as f: f.write(Hello) # 无论是否发生异常文件都会被关闭 # 输出 # Opening file test.txt # Closing file test.txt__exit__方法接收三个参数异常类型、异常实例、回溯对象。如果没有异常发生它们都是None。使用contextlib.contextmanager装饰器更简洁的方式创建上下文管理器。from contextlib import contextmanager import time contextmanager def timer(): start time.perf_counter() try: yield # 在此处进入with块 finally: end time.perf_counter() print(fElapsed time: {end - start:.4f} sec) with timer(): time.sleep(1) # 输出Elapsed time: 1.0012 sec被contextmanager装饰的函数必须是一个生成器且只yield一次。yield之前的部分相当于__enter__之后的部分在finally中相当于__exit__。6.3 描述符属性访问的幕后推手描述符是实现了__get__、__set__或__delete__方法的类。它允许你自定义属性访问、赋值和删除的行为。这是实现property、类方法、静态方法等高级特性的基础。一个简单的验证描述符class PositiveNumber: def __init__(self, name): self.name name # 属性名 def __get__(self, obj, objtypeNone): if obj is None: return self # 通过类访问时返回描述符实例本身 return obj.__dict__.get(self.name, 0) # 从实例的__dict__中获取值 def __set__(self, obj, value): if value 0: raise ValueError(f{self.name} must be positive) obj.__dict__[self.name] value # 将值存储在实例的__dict__中 def __delete__(self, obj): del obj.__dict__[self.name] class Order: quantity PositiveNumber(quantity) # 描述符实例作为类属性 price PositiveNumber(price) def __init__(self, quantity, price): self.quantity quantity # 触发 PositiveNumber.__set__ self.price price def total(self): return self.quantity * self.price order Order(5, 10) print(order.total()) # 输出50 order.quantity -1 # 触发 ValueError: quantity must be positive当访问order.quantity时Python发现quantity是类Order的一个描述符属性于是调用PositiveNumber.__get__(quantity, order, Order)。赋值操作同理。描述符将验证逻辑集中在了PositiveNumber类中实现了关注点分离。property的本质property本身就是一个描述符。# 使用property装饰器 class Circle: def __init__(self, radius): self._radius radius property def radius(self): return self._radius radius.setter def radius(self, value): if value 0: raise ValueError(Radius must be positive) self._radius value property def area(self): return 3.14159 * self._radius ** 2 # 等价的手动property实现 class CircleManual: def __init__(self, radius): self._radius radius def get_radius(self): return self._radius def set_radius(self, value): if value 0: raise ValueError(Radius must be positive) self._radius value radius property(get_radius, set_radius) # property是一个描述符类 property def area(self): return 3.14159 * self._radius ** 2property(fgetNone, fsetNone, fdelNone, docNone)返回一个描述符对象。property装饰器只是语法糖它创建了一个property描述符并将其绑定到类属性上。理解描述符你就理解了Python属性访问的整个魔法体系。
返回列表