python中创建写用什么字母 python中创建二维数组
论文探讨了在Python中创建一种特殊单例对象的多种策略,该对象同时需要作为类型提示和特定值使用,类似None。文章分析了使用None和省略的局限性,重点推荐了自定义单例类作为最实用且Pythonic的解决方案,并利用元类实现“类即介绍实例”的进阶方法及初步的类型检查拓扑问题,旨在为开发者提供全面的选择指南。 1. 问题背景:自定义“未设置”值
在python开发中,我们经常遇到区分需要“字段未提供(未设置)”这两种状态的场景。例如,在一个partial_update方法中,如果某个字段的值明确设置为none,表示需要字段字段更新为non e;而如果该字段完全没有提供,则表示不能接触该字段。为了实现这种区分,我们需要一个特殊的单例对象,例如notset,它既能作为默认值指示参数未提供,又可以作为提示类型的一部分。
考虑以下Client类中的partial_update方法示例:class客户端: defpartial_update( self, obj_id: int, obj_field: int | None | NotSet = NotSet, # 期望 NotSet 既是类型也是值 another_field: str | None | NotSet = NotSet, # ... 其他字段 ): # 如果 obj_field 未明确指定,则如果 obj_field 为 NotSet,则不更新: print(quot;obj_field 未设置,不更新。quot;) else: # 否则,更新 obj_field,即使其设置为 None print(fquot;obj_field 设置为: {obj_field},执行更新。quot;) if another_field 未设置: print(quot;another_field 未设置,不更新。quot;) else: print(fquot;another_field 为: {another_field},执行更新。quot;)登录后复制
我们的目标是找到一种方式来定义NotSet,导出能够像None一样,既可以作为提示obj_field的类型: int |无 | NotSet的一部分,也可以作为默认值obj_field = NotSet使用,并且obj_field是NotSet能够正确判断。2. 常见的尝试及限制
在上述实现NotSet时,有几种常见的尝试,但它们各自存在一定的限制。2.1使用None
直接使用None作为“未设置”的标志是最引人注目的,但往往不可行。限制:None在许多业务逻辑中被用于表示“空值”或“缺失值”,这意味着字段本身可能是可空的。
如果None同时表示“未设置”和“设置为None”,会导致语义不一致,无法区分用户是想将字段更新为None还是根本没有提供该字段字段。2.2使用内置Ellipsis (...)
Python提供了Ellipsis对象,写作...。通常是一个单例,可以用于某些特殊场景。
立即学习“Python免费学习笔记(深入)”;from types import EllipsisTypedefpartial_update_with_ellipsis( obj_field: int | None | EllipsisType = ...,): if obj_field is ...: print(quot;obj_field 未设置 (使用省略号)quot;) else: print(fquot;obj_field 设置为: {obj_field}quot;)partial_update_with_ellipsis()partial_update_with_ellipsis(None)partial_update_with_ellipsis(10)登录后复制
成像:成像不明显:省略号通常用于切片或占位符,将其用于表示“未设置”可能不够直观和明确,降低代码的准确性。 类型提示限制:虽然可以使用EllipsisType作为类型提示,但直接在类型提示中写...(例如obj_field:int |无 | ... = ...)不是被Python类型系统支持的。2.3 自定义单例类(初步尝试)
一个自定义的单例类创建是更接近目标的方法。class NotSetType: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instanceNotSet = NotSetType()defpartial_update_custom_singleton( obj_field: int | None | NotSetType = NotSet, # 注意这里是 NotSetType): if obj_field is NotSet: print(quot;obj_field 未设置 (自定义单例)quot;) else: print(fquot;obj_field 设置为: {obj_field}quot;)partial_update_custom_singleton()partial_update_custom_singleton(None)partial_update_custom_singleton(20)登录后复制
局限性:这是目前状况最好的方案。创建了一个明确的单例NotSet,并且在运行时可以正确判断obj_field是否为NotSet。
然而,在类型提示时,我们不得不使用其类名NotSetType,而不是我们期望的实例名NotSet。虽然功能上没有问题,但与我们最初“NotSet既是类型又是值”的理想目标存在计算的语法差异。3. 推荐实践:Pythonic的自定义单例模式
尽管上述要求的类型提示差异,自定义单例类仍然是实现“未设置”值最推荐、最Pythonic且最实用的方法。它提供了清晰的语义、良好的认知性,并且在运行时存在表现稳定性。
实现原理:通过重写类的__new__方法,我们可以控制对象的创建过程。在__new__中,我们检查类是否已经有了实例(通常存储在一个私有类变量_instance中)。如果还,则调用父类的__new__方法来实例并保存;如果已经存在,则直接返回现有的实例。这样确保了无论多少次尝试创建该类的对象,都只能得到同一个实例。#定义 NotSetType 类 NotSetType: _instance = None # 用于存储单例实例 def __new__(cls): quot;quot;quot;重写 __new__ 方法以确保只创建一个实例。 quot;quot;quot; if cls._instance is None: # 如果实例不存在,则创建并存储 cls._instance = super().__new__(cls) return cls._instance # 返回唯一的实例 def __repr__(self): quot;quot;quot;为实例提供友好的字符串表示。 quot;quot;quot; return quot;lt;NotSetgt;quot; def __str__(self): quot;quot;quot;为实例提供友好的字符串表示。 quot;quot;quot; return quot;NotSetquot;# 创建 NotSet 单例实例NotSet = NotSetType()# 实例应用class UserProfile: def __init__(self, name: str, email: str | None = None): self.name = name self.email = email def update_profile( self, name: str | NotSetType = NotSet, email: str | None | NotSetType = NotSet ): quot;quot;quot;根据提供的参数更新用户数据。 NotSet,则不更新对应字段。
quot;quot;quot; 如果姓名未设置: self.name = 姓名 print(fquot;更新姓名: {self.name}quot;) 如果邮箱未设置: self.email = email print(fquot;更新邮箱: {self.email}quot;) print(fquot;当前资料: Name={self.name}, Email={self.email}quot;)# 使用示例user = UserProfile(quot;Alicequot;, quot;alice@example.comquot;)print(quot;---灵魂资料 ---quot;)print(fquot;姓名: {user.name}, Email: {user.email}quot;)print(quot;\n--- 第一次更新 (只更新姓名) ---quot;)user.update_profile(name=quot;Aliciaquot;)print(quot;\n--- 第二次更新 (更新邮箱为 None) ---quot;)user.update_profile(email=None)print(quot;\n--- 第三次更新 (同时更新姓名和邮箱) ---quot;)user.update_profile(name=quot;Bobquot;, email=quot;bob@example.comquot;)print(quot;\n--- 第四次更新 (不提供任何参数) ---quot;)user.update_profile()# 验证 NotSet 的单例特性assert NotSet is NotSetType()print(fquot;\nNotSet 的类型为: {type(NotSet)}quot;)print(fquot;NotSet 的值为:{NotSet}quot;)登录后复制
注意事项:这种方法在功能上完全满足需求。虽然在类型时提示需要使用NotSetType,而不是直接使用NotSet实例,但这种差异通常是不可避免的,它明确表达了obj_field所需的或NotSetType的实例。对于大多数Python项目而言,兼顾了这是一个明确性、可执行性和实用性的最佳选择。4. 进阶探讨:利用元类实现“类即实例”
为了严格实现“NotSet既是类型又是值”的目标,即type(NotSet) == NotSet,我们可以利用Python的元类机制。方法非常解决,但是伴随着复杂的实现和潜在的这种兼容性问题。
实现原理:一个类的类型就是它的元类。如果一个类成为自己的类型,那么它的元类在创建这个类的时候,需要返回这个类本身的一个实例。这听起来有点绕,但是通过元类Meta的__new__方法可以实现。class Meta(type): quot;quot;quot;自定义元类,用于创建一个类,创建者在创建时返回其自身的一个实例。
quot;quot;quot; def __new__(cls,name,bases,dct): # 调用父类(type)的__new__来创建NotSet类本身actual_class = super().__new__(cls,name,bases,dct)#然后,通过调用刚刚的类(actual_class)来获取它的一个实例#这里的actual_class()会调用NotSet类的__new__方法#如果 NotSet 类没有自定义 __new__,调用类型的 __new__ # 关键在于,我们返回的是这个类的“实例”,而这个“实例”就是这个类本身 returnactual_class() # 返回 NotSet 类的实例class NotSet(type,metaclass=Meta): quot;quot;quot;使用元元类创建的 NotSet 类。它的行为使得 NotSet 既是类型又是其自身的实例。
quot;quot;quot; def __new__(cls): #确保 NotSet 也是一个例子,虽然 Meta 元类已经处理了大部分 if not hasattr(cls, '_instance'): cls._instance = super().__new__(cls) return cls._instance def __repr__(self): return quot;lt;NotSetgt;quot; def __str__(self): return quot;NotSetquot;# 验证 NotSet 的特殊行为 print(fquot;NotSet: {NotSet}quot;)print(fquot;type(NotSet): {type(NotSet)}quot;)print(fquot;NotSet is type(NotSet): {NotSet is type(NotSet)}quot;) # 预期为 True# 结尾应用 (与之前的partial_update 类似)defpartial_update_metaclass( obj_id: int, obj_field: int | None | NotSet = NotSet,# 现在 NotSet 可以直接作为类型提示): if obj_field is NotSet: print('obj_field 未设置 (元类实现)') else: print(f'obj_field 设置为: {obj_field}')partial_update_metaclass(1)partial_update_metaclass(1, None)partial_update_metaclass(1, 42)登录后复制
重要注意:这种元类方法确实实现了type(NotSet) == NotSet 的语义,允许 NotSet 直接类型作为提示使用。然而,这是一个非常规范的 Python 模式,它会引入显着的复杂性,并且对静态类型检查器(如 Mypy)来说可能是一个挑战。Mypy 通常会认为 Not Set是一个类,是一个可以作为值的类型。这意味着你的代码在运行时可能正常工作,但在静态分析阶段会报告错误或警告。因此,强烈不建议在生产环境中使用此方法,因为它牺牲了可维护性和类型检查的可靠性。5. 替代设计:使用**kwargs处理可选参数
在某些情况下,如果可选参数的数量非常多,或者参数的组合非常动态,使用**kwargs可能是一个更简洁的设计模式,尽管它牺牲了部分类型提示的优势。
class Client: def __init__(self,initial_obj_id: int): self.obj_id = initial_obj_id self.obj_field = None self.another_field = quot;defaultquot; defpartial_update_kwargs(self, **kwargs): quot;quot;quot; 使用 **kwargs 更新对象属性。 quot;quot;quot; print(fquot;--- 更新前: obj_id={self.obj_id}, obj_field={self.obj_field}, another_field={self.another_field} ---quot;) for field,value in kwargs.items(): if hasattr(self, field): setattr(self, field, value) print(fquot;更新字段 '{field}' 为: {value}quot;) else: print(fquot;警告:字段 '{field}' 不存在。quot;) print(fquot;--- 更新后: obj_id={self.obj_id}, obj_field={self.obj_field}, another_field={self.another_field} ---quot;)#使用示例client_instance = Client(100)client_instance.partial_update_kwargs(obj_field=50)client_instance.partial_update_kwargs(another_field=quot;new_valuequot;, obj_field=None)client_instance.partial_update_kwargs() # 不提供任何参数,不更新登录后复制
权衡分析:优点:极大地简化了函数签名,尤其适用于有大量任选参数的场景。调用方只需提供需要更新的字段。缺点: 失去了明显式的类型提示。**kwargs中的键和值的类型在签名中无法直接表达,这降低了代码的判断性和IDE的自动补全能力。另外,如果确定了不存在的字段名,只能在运行时检查。6. 总结与建议
在Python中创建既能作为类型提示又特定值的单例对象是一个常见需求,尤其是在处理“未设置”语义时。
首选方案:自定义单例类(NotSetType和NotSet实例)是最推荐、最实用且符合Python习惯的方法。它提供了明确的语义,易于理解和维护。类型提示是NotSetTypeNotSet,但通常是简洁的折衷,且在功能上完全满足需求。
不推荐方案:元类实现“类即实例”虽然技术上可以实现NotSet既是类型又是其自身实例的严格要求,但这种方法过于复杂,且与静态类型检查器(如Mypy)的兼容性差。强烈建议避免在生产环境中在使用这个方案时,因为它会引入不必要的复杂性和潜在的类型检查问题。
替代设计:使用`kwargs`**对于参数数量多且动态性强的场景,**kwargs提供了一种灵活的解决方案。然而,它牺牲了具有显着式提示类型和参数名检查的优势,应根据项目需求权衡利弊。
综上所述,对于大多数需要区分“无值”和“未设置”的场景,采用自定义单例类(如论文中NotSetType和NotSet的)实现)是最佳实践。它在清晰性、可维护性和功能性之间取得了良好的平衡。
以上就是Python中创建既作类型又作值的单例对象:策略与权衡的详细内容,更多请关注乐哥网其他常识文章相关!