Python中的高阶概念属性:五个你应该搞明白的知识点
在现代编程世界中,面向对象编程(OOP)语言在改变软件开发中的设计和实现模式方面发挥了进化作用。作为OOP家族的重要成员,Python在过去10年左右逐渐流行起来。与其他OOP语言一样,Python围绕大量不同的对象操作其数据,包括模块、类和函数。
如果您有任何OOP语言的编程经验,您应该知道所有对象都有其内部特征数据,称为字段、属性或属性。在Python中,这些对象绑定的特征数据通常称为属性。在本文中,我将特别在自定义类的上下文中讨论它们
01
类属性
为了更好地管理项目中的数据,我们经常需要创建自定义类。在Python中,类也是对象,这意味着它们可以有自己的属性。让我们看一个例子。
>>> class Dog:
... genus = "Canis"
... family = "Canidae"
...
>>> Dog.genus
'Canis'
>>> Dog.family
'Canidae'
>>> dir(Dog)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'family', 'genus']
如上所示,我们声明了一个名为Dog的类。因为所有的狗都属于犬类属和犬科家族,所以我们创建了两个类属性,分别命名为属和科来存储这两条信息。如您所见,我们可以直接使用类来访问这些属性。我们可以使用函数dir来显示狗的属性列表,其中包括家族和属。
这些定义为类级别的属性称为类属性,类可以直接检索它们。但是,与其他OOP语言不同,Python中的实例对象也可以直接访问这些类属性,如下面的代码片段所示。
>>> Dog().genus
'Canis'
'>>> Dog().family
'Canidae'
02
实例属性
通过自定义类,我们还可以为实例对象设置属性。这些属性称为实例属性,这意味着它们是特定于实例的数据。让我们继续dog class。
>>> class Dog:
... genus = "Canis"
... family = "Canidae"
...
... def __init__(self, breed, name):
... self.breed = breed
... self.name = name
...
在上面的代码中,我们定义了__init__函数,它将作为创建一个新的Dog实例的构造方法。第一个参数self引用了我们正在创建的实例。在实例化期间(即创建新实例),我们将为新实例对象分配品种和名称信息,这些属性将成为实例特征的一部分,如下所示。
>>> dog = Dog("Rottweiler", "Ada")
>>> dog.name
'Ada'
>>> dog.breed
'Rottweiler'
需要注意的一点是,我们可以为具有与class属性相同的属性的实例赋值。在这种情况下,当您检索实例的这个属性时,将不会检索class属性。换句话说,当您使用一个实例对象来检索class属性时,Python将首先检查实例本身是否有一个用相同名称设置的属性。如果没有,Python将使用class属性作为回退。此外,设置一个实例的属性不会影响同名类的属性。让我们在下面的代码片段中看看这些特征。
>>> dog.genus = "Felis"
>>> dog.genus
'Felis'
>>> Dog('Poodle', 'Cutie').genus
'Canis'
03
函数作为属性
在Python中,一切都是对象,前面我已经提到类是对象。此外,函数是Python对象。在类中,我们可以定义函数,通常称为方法。根据使用这些函数的方式,我们可以将它们进一步分类为类方法、静态方法和实例方法。在这里,理解这些差异并不是必须的。
尽管某些OOP语言将属性(或属性)和函数视为不同的实体,但Python将这些方法(函数)视为类的属性——与我们前面定义的类属性没有太大区别。让我们用上面提到的三种方法来更新Dog类:类方法、静态方法和实例方法,如下所示。
>>> class Dog:
... genus = "Canis"
... family = "Canidae"
...
... def __init__(self, breed, name):
... self.breed = breed
... self.name = name
...
... @classmethod
... def from_tag(cls, tag_info):
... breed = tag_info["breed"]
... name = tag_info["name"]
... return cls(breed, name)
...
... @staticmethod
... def can_bark():
... print("Yes. All dogs can bark.")
...
... def bark(self):
... print("The dog is barking.")
...
对于更新后的类,我们可以使用函数dir检查类的属性列表。如下所示,类方法和静态方法都包含在列表中。
>>> dir(Dog)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'bark', 'can_bark', 'family', 'from_tag', 'genus']
然而,有一件事可能会让一些人感到惊讶,那就是该列表中包含了实例方法bark。我们知道,实例方法是那些由实例对象调用的函数,因此有些人可能认为这些实例方法应该绑定到所有单独的实例。然而,在Python中却不是这样。在解释实例方法如何工作之前,让我们先看看下面的代码。
>>> dog = Dog("Rottweiler", "Ada")
>>> dog.bark()
The dog is barking.
>>> Dog.bark(dog)
The dog is barking.
如上所示,我们首先创建了Dog类的一个实例。与其他OOP语言一样,实例对象可以直接调用实例方法bark。然而,Python与其他语言的不同之处在于,实例方法的调用是通过类来操作的,通过传递实例作为参数来调用定义的函数(即,dog .bark(dog))。换句话说,instance.inst_method()在本质上与Python中的Class.inst_method(instance)相同。
之所以可以这样做,是因为Dog类“拥有”实例方法,这是一种节省内存的机制,因为Python不需要为每个实例对象创建单独的函数副本。相反,当一个实例调用一个实例方法时,Python将调用委托给类,该类将通过传递实例调用相应的函数(它将被设置为已定义函数中的self参数)。
04
私有属性
如果您有OOP的经验,就不应该不熟悉访问修饰符的存在,比如public、private和protected。这些修饰符限制了可以访问修改的属性和函数的范围。然而,您很少在Python中听到这样的讨论。实际上,如果借用OOP中的术语,所有Python属性都是公共的。如上所示,在类和实例可以访问的地方,类和实例属性都可以自由访问。因此,严格地说,Python中没有真正的私有或受保护的属性(后面将讨论)。我们只是类比地使用这些术语,以便来自其他OOP背景的程序员更容易理解相关的编码约定(是的,只是一种约定,没有作为真正的访问控制加以加强)。
让我们首先讨论一下如何在Python中定义“私有”属性。惯例是用两个前导下划线命名这些属性,并且不超过一个后引下划线。考虑下面更新过的Dog类的示例—为了简单起见,我们省略了前面定义的其他属性。
>>> class Dog:
... def __init__(self, breed, name):
... self.breed = breed
... self.name = name
... self.__tag = f"{name} | {breed}"
...
>>> dog = Dog("Rottweiler", "Ada")
>>> dog.name
'Ada'
>>> dog.__tag
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Dog' object has no attribute '__tag'
在上面的更新之后,Dog实例将拥有一个名为tag的私有属性,正如其名称所示。实例对象仍然可以像以前一样访问它的其他属性(例如,名称)。然而,实例不能访问私有属性剩余的标记,这可能是我们所期望的。实际上,这种对访问这些属性的限制正是它们被称为“私有”属性的原因。但它是怎么发生的,在引擎盖下?毕竟,我前面提到过,所有Python属性在默认情况下都是公共的。下面将向您展示Python如何实现“私有”属性。
>>> dog.__dict__
{'breed': 'Rottweiler', 'name': 'Ada', '_Dog__tag': 'Ada | Rottweiler'}
>>> dog._Dog__tag
'Ada | Rottweiler'
__dict__特殊方法(也称为dunder方法,在名称前后都有双下划线)能够显示对象的字典表示。具体来说,字典中的键-值对是对象的属性及其值。正如我们所看到的,除了bread和name属性之外,还有一个名为_dog__tag标记的属性。这个属性正是私有属性__tag通过一个称为mangling的过程与对象关联的方式。
具体来说,mangling或name mangling是使用_ClassName作为私有属性的前缀,这样我们就人为地创建了对这些“私有”属性的访问限制。但是,如果我们确实想检索任何私有属性,我们仍然可以使用被破坏的名称访问它,就像我们在代码片段中使用_dog__标记所做的那样。
05
受保护的属性
在上一节中,我们讨论了私有属性,但是受保护的属性呢?Python中与受保护属性对应的属性名称只有一个下划线。不像双下划线会导致混乱,单下划线前缀不会改变Python解释器处理这些属性的方式——它只是Python编程世界的一个惯例,表示他们(例如,编码器)不希望你访问这些属性。但是,如果你坚持要访问它们,你仍然可以这样做。让我们看看下面的代码。
>>> class Dog:
... def __init__(self, breed, name):
... self.breed = breed
... self.name = name
... self.__tag = f"{name} | {breed}"
... self._nickname = name[0]
我们通过创建一个名为_nickname的实例属性来更新类Dog。正如其名称使用下划线前缀所表明的那样,按照约定,它被认为是一个“受保护”的属性。我们仍然可以将这些受保护的属性作为其他“公共”属性来访问,但是一些ide或Python编辑器不会为这些非公共属性提供提示(例如,自动完成提示)。有关这些使用Jupyter笔记本的例子,请参见屏幕截图。
如果我们使用模块而不是类,就像我们在这里所做的那样,当我们使用from _module import *导入模块时,带有下划线前缀的名称将不会被导入,从而提供了一种机制来限制对这些“受保护的”属性的访问。
·END·
- C++ 隐式类型转换
- IE漏洞调试之CVE-2013-3893
- C++ STL之迭代器注意事项
- 设计3D标签为什么要有一个字符间隙tracking?为什么要重写getPrefferedSize()?画三遍的顺序有讲究
- C++STL之整理算法
- Offset2lib攻击测试:看我如何全面绕过64位Linux的内核防护
- C++ STL之查找算法
- 教你一招 | Python3新特性(一) :字符串
- C++ STL之set的基本操作
- Android ClassLoader详解
- 揭秘银行木马Chthonic:网银大盗ZeuS的最新变种
- C++STL之map的基本操作
- android EventBus 3.0使用指南
- C++ STL之deque的基本操作
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 图解面试题:如何找到喜欢的电影?
- Java agent 与 byte buddy
- 关于TRTC云端混流的踩坑分享
- 聊聊dubbo-go的ConsistentHashLoadBalance
- R语言用WinBUGS 软件对学术能力测验(SAT)建立分层模型
- R语言使用随机技术差分进化算法优化的Nelson
- R语言用神经网络改进Nelson-Siegel模型拟合收益率曲线分析
- R语言和QuantLib中Nelson-Siegel模型收益曲线建模分析
- 用R语言用Nelson Siegel和线性插值模型对债券价格和收益率建模
- R语言LME4混合效应模型研究教师的受欢迎程度
- R语言Black Scholes和Cox-Ross-Rubinstein期权定价模型案例
- R语言中的风险价值模型度量指标TVaR与VaR
- R语言用线性回归模型预测空气质量臭氧数据
- R语言线性模型臭氧预测: 加权泊松回归,普通最小二乘,加权负二项式模型
- R语言中回归和分类模型选择的性能指标