上期Python专栏,为大家介绍了Python对象是如何被构造的,以及类与对象的本质。大家有没有觉得收获满满呢?戳这里可以复习下。
今天来给大家讲讲新的硬核知识,经常看Python中文教程的朋友一定会发现,我们讲的新内容其实是现有Python中文教程里不常见的哦。
如何手动制造一个对象?
基于上期的分析,类和对象的本质已经初见端倪——类和对象本质上也是一种映射结构,这一结构中存值的那一部分位于 __dict__ ,而存储业务逻辑的部分则是各个函数,它们在 dir(t) 中均可以找到名称,并且可以通过 getattr 进行访问(实际上在Python中,函数也同样是一个对象)。
因此,我们可以基于上述的原理,尝试构造一个简易的对象出来。例如下面的例子:
class MyObject(object):
pass
if __name__ == '__main__':
t = MyObject() # the same as __new__
t.x = 2 # the same as __init__
t.y = 5
def plus(z):
return t.x + t.y + z
t.plus = plus # the same as function def
print(t.x, t.y)
print(t.plus(233))
首先在第6行,我们模仿 __new__ 方法的思路,手动创建一个空对象(注意不能直接用 object ,而需要继承一层,具体原因详见官方文档中的Note部分);
接下来分别对对象的属性进行赋值,包括数值 x 和 y ,以及一个会基于 t.x 和 t.y 进行运算处理的函数 plus (一般我们更习惯于称之为方法);
最后就是使用这一手动创建的对象,可以看到 t.x和t.y均可正常使用,并且方法t.plus(z)也可以被正常调用。经过这一系列操作,一个手工创建的对象就产生了,而且从使用者的角度来看,也和正常实例化的对象并无差异。
如何手动制造一个类?
不仅对象,类也是可以手动制造出来的。话不多说,我们先看看来自官方文档的构造 type 类说明
看起来挺长,不过后续附了一个最为简明扼要的例子。
# first code
class X:
a = 1
# second code, the same as the former one
X = type('X', (), dict(a=1))
所以其实依然不难理解,简单来说就是三个基本参数:
-
名称( name )——字面意思,表示构造的类名
-
基类( bases )——字面意思,表示所需要继承的基类
-
字典( dict )——即需要赋予对象的属性
因此基于以上的原理,我们可以构造出来一个自己的类,就像这样:
def __init__(self, x, y):
self.x = x
self.y = y
def plus(self, z):
return self.x + self.y + z
XYTuple = type('XYTuple', (), dict(
__init__=__init__,
plus=plus,
))
if __name__ == '__main__':
t = XYTuple(2, 5)
print(t.x, t.y)
print(t.plus(233))
# 2 5
# 240
# The definition of class is exactly the same as :
# class XYTuple:
# def __init__(self, x, y):
# self.x = x
# self.y = y
#
# def plus(self, z):
# return self.x + self.y + z
不难发现,从这样的视角来看,一个类的装配也大致分为三步:
-
“初始化阶段”——此阶段会创建一个指定名称的类对象
-
“继承阶段”——此阶段会尝试在类对象上建立与已有类的继承关系
-
“装配阶段”——次阶段会将类所需的各个属性,装配至类对象上
至此,经过了三个阶段后,一个类对象创建完毕,并且在使用上和正常定义的类并无差别。
私有字段的本质?
对于了解Python面向对象或学习过Java、C++等其他语言的读者,应该对私有字段这个东西并不陌生(如果还不够了解的话可以看看Python3 面向对象 - 类的私有属性)。
Python3面向对象-类的私有属性参考地址:
https://www.runoob.com/python3/python3-class.html
在Python中,我们所熟知的私有字段大致是如下的形态:
class T:
def __init__(self):
self.__private = 1 # private field, starts with __
self._protected = 2 # protected field, starts with _
self.public = 3 # public field, starts with alphabets
简单来说就是:
-
私有字段,仅可以被类内部访问,以双下划线开头
-
保护字段,可以被当前类及其子类访问,以单下划线开头
-
公有字段,可以被自由访问,以字符开头
因此对上面的例子中,实际访问效果如下:
t = T()
t.__private # Attribute Error!
t._protected # 2
t.public # 3
保护字段和公有字段是可以被访问到的,但是一般情况下,保护字段并不推荐在当前类或子类以外的地方进行访问(实际上当你这么做的时候,不少IDE都会报出明确的warning),而私有字段则无法访问,直接访问会导致报错。看起来似乎一切很正常,但是让我们来看看上面例子中变量 t 内部都有什么:
t.__dict__ # {'_T__private': 1, '_protected': 2, 'public': 3}
其中 public 和 _protected 是意料之内的,但是除此之外还包含一个_T__private,并且其值正是在构造函数中所赋予的值。基于这一点,我们再来做个实验。
t._T__private # 1
发现私有字段居然也可以被访问。至此,我们可以得出一个结论——在Python中,并不存在严格意义上的私有字段,我们所知道的私有字段本质上更像一种语法糖效果,而保护字段则干脆是被摆在明面上的。
从这个角度来看不难发现,在Python中这些字段之所以还能起到私有字段或保护字段应有的效果,本质上靠的是开发者意义上的约束,而非语言系统本身的强制力。这一点和Java等静态语言存在本质上的差异,在Java中定义的私有字段一般无法通过正常途径进行访问,即便通过反射机制强制读取,也需要绕开一系列机制。
🥳 下期预告
本文重点针对类的特性,从原理角度进行了分析。在本系列的下一篇中,会重点针对类的方法和属性进行讲解,以及treevalue第三弹也将会在不久后推出,敬请期待。
👏:欢迎大家体验OpenDILab开源的项目https://github.com/opendilab
🪐:作者小哥的开源项目(部分仍在开发中)
命令行工具:https://github.com/HansBug/plantumlcli
对象转可执行代码:https://github.com/HansBug/potc
好用的工具库:https://github.com/HansBug/hbutils
扫码即可了解更多开源信息~