“ 凡是用python写过面向对象编程的都知道,你的所有的对象的方法都是需要传入一个self参数的,那么这个self到底是什么呢?”
onlybugs warning:没做好放弃心理准备者请勿观看此文!
本文纯属技术类杂文 非数据分析/生物信息必会技术
Python的兴起导致了越来越多的人都开始使用这一门语言。在生物信息学领域,大数据分析领域,轻量级网站开发领域,深度学习领域等等,Python已经成为了大多数人必不可少的一个工具。
加上信息时代,到处都是Python的广告,什么不学Python就落伍了,学了Python解放双手,学Python挑战年薪百万等等,导致大家一窝蜂的涌入了py神奇的世界。
但是由于计算机知识的匮乏以及缺乏良好的教学环节,最后大家都成为了熟练工种,只能知其然而不知其所以然,听着云里雾里的计算机名词,想着自己什么时候能够称霸一方,可手上却止不住的import(生活所迫,换句话说,我其实站在了巨人的肩膀上!
后来很多人不满足于约定俗成的写法,想深入探究代码为什么这么写,也会经历一段痛苦时光。由于本人亲身经历过,所以知道探索的路上有多痛苦,所以也希望大家能继续下去。而在探索过程中,有一座名叫面向对象的大山,是你必须要面对的,本文主要讲解关于面向对象中的self到底是什么,灵感来自于某一天上午,然后今天有空就简要的探索了一下。
全文分三个大部分,第一个部分是关于类和面向对象简介并且给出简单例子,第二部分介绍对象以及self参数,第三部分简要介绍一下对象的属性(成员)。
面向对象和类
面向对象,英文叫做OOP,它本身是一种程序设计的思想。说起面向对象就必须先描述一下面向过程的概念。面向过程,就是把一切都看做过程,比如你做饭,买菜,洗菜,切菜,炒菜,然后才能吃,这就是一个面向过程的经典案例。
但是面向对象是什么呢,也拿做饭吃饭举例子,你去饭店说,我要什么菜,然后老板给你做,端上来你吃,这就是一个面向对象的过程。你会说看起来好像差不多,但是你仔细想想,在这个过程中,你(对象)只不过是执行了一些动作(方法),然后饭店老板(对象)给你做好了(方法),你吃(方法)。
整个过程中是两个对象在互作,其中又使用了一些其它对象,这个过程更像我们的现实生活。所以面向对象编程模型是一种非常非常非常流行的编程范式。
而我们使用的Python语言,大家可能都听过,一切皆对象,任何东西其实都是个对象,而我们也可以定义自己的对象。其中,定义对象的前提是需要有一个类。类可以说是从很多个个体中抽象出来的一个统称。
比如,土豆,茄子,西红柿,豆角,白菜等,我们都叫它们蔬菜,那蔬菜就是一个类。而金针菇,白玉菇,海鲜菇,口菇等等,都是蘑菇,于是它们可以是一类,蘑菇类。这么说大家就知道什么是类了吧。
下面给个简单的类的定义。
class People(object):
def __init__(self,name,site) -> None:
self.name = name
self.site = site
这样我们就定义了一个最简单的类,名字叫做人,下面的init是约定俗成的写法,照着写就行,篇幅有限不多介绍为什么有init,感兴趣自己查。init被称为构造方法,就是你要创建对象的时候给对象初始化一些参数,比如这里我初始化了姓名和职位。
对象和self
上文中简单说了啥叫类,这里就要讲什么叫对象。这里不整花里胡哨的,我这么来解释对象。如果说类是你抽象出来的一个笼统概念,对象就是把笼统概念又进行了具体化,具体化的结果叫对象,这个过程叫实例化。看着还是很抽象,给个案例解释,我们上面定义的People类只能说你是笼统上的人,你应该具有名字和职位,但是你还没出生,所以你只能是抽象的,不具有实体的。
而如果我把你具体化了,你叫Ann,职位是1,那么你就是一个对象(实例).而在Python中具体是这么实现的
class People(object):
def __init__(self,name,site) -> None:
self.name = name
self.site = site
def MyName(self):
print("My name is {} and my site is {}".format(self.name,self.site))
Ann = People('Ann',1)
Ann.MyName()
# My name is Ann and my site is 1
这里就是一个简单的实例化过程,我给每个人提供了一个动作,这种动作,我们一般使用函数来实现,称为方法,其实就是函数罢了。
上面就是把一个对象进行实例化的过程,并且让这个对象具有说你叫什么的动作。面向对象好就好在一个类可以多个实例化
Ann = People('Ann',1)
Dnn = People("Dnn",2)
Gnn = People("Gnn",1)
Ann.MyName()
Dnn.MyName()
Gnn.MyName()
# My name is Ann and my site is 1
# My name is Dnn and my site is 2
# My name is Gnn and my site is 1
这里通过实例化了三个人,并且让他们都自报家门展示了什么叫对象和类的实例化。
接下来就是最关键的,self到底是啥?
class People(object):
def __init__(self,name,site) -> None:
self.name = name
self.site = site
def MyName(self):
print("My name is {} and my site is {}".format(self.name,self.site))
你可能观察到了,我们的每个动作都传了一个叫做self的参数,而且是第一个参数,这是约定俗称的,我先来说个死知识,类的所有成员方法(除了静态方法和类方法或者加了特殊装饰的方法),第一个参数必须是self。如果你不知道我在说什么什么鬼方法,那你就记住,你使用的方法必须都在第一个位置写self参数。
接下来,我来解释为什么要这么做,以及self的真身。我们都知道,面向对象具有三大特性,第一个特性叫做封装,就是咱们内部玩的东西就咱们自己玩,不给外人玩。那你自己玩的时候,总得知道你要玩的东西叫啥吧,所以self的第一个作用就是能让你找到整个对象内部的方法,成员,方便咱们内部自己玩。你可以想想,如果没有了self,我想用name成员,我找都找不到。
而第二个作用,紧跟着第一个作用的最后。假如现在咱们不加self参数,咱们三个人,大家都需要在自报家门函数里调用name,那你能还能找到你自己的名字吗?你知道这name是你的还是我的啊。
最后,上点终极硬货,也就是self到底长啥样。
class Life(object):
def __init__(self) -> None:
self.what_life = "Life is to lie down and make some fries every day!"
print(self)
# print(hex(id(self.what_life)))
l1 = Life()
a1 = input("Eat ribs and elbows:")
这里我使用了一个新类,叫做生活,只有一个成员,是一个字符串,人生嘛,就是躺着和搞点薯条。然后我这里输出了self,很多人可能都没试过吧,输出self,可真是一件美事啊。
<__main__.Life object at 0x0000028F01C2D1C0>
Eat ribs and elbows:
这就是我们的self,翻译翻译,它是啥啊,它是Life的一个对象,它在哪呢?在内存地址为0x0000028F01C2D1C0的地方。接下来,我就带大家去看看,这self到底长个什么锤子样?
这里我使用了xdbg,因为python是64位的,所以没法使用OD,
看到了吗,这里的最上面一行开始,就是我们的self的真身了,由于我没读过Python源码,所以我下面的都是推论。Python的对象首先肯定是堆区的(废话,正经人谁把对象放栈里啊),然后对象应该具有唯一标识符的,推测是前面的01位置,然后大家可以看到,这里有很多个02 8F 01 啥啥啥的东西,跟我们的对象地址很像,这就是我们下面要讲解的。
对象属性的存储
最后,简单讲讲关于Python对象是如何存储成员的,并且给个有趣的案例。
我可以改变 what_life的内容,你信不信?
class Life(object):
def __init__(self) -> None:
self.what_life = "Life is to lie down and make some fries every day!"
print(self)
print(hex(id(self.what_life)))
l1 = Life()
print(l1.what_life)
a1 = input("Eat ribs and elbows:")
print(l1.what_life)
# 正常输出
# __main__.Life object at 0x0000021621E3D1C0>
# 0x2162192b9d0
# Life is to lie down and make some fries every day!
# Eat ribs and elbows:f
# Life is to lie down and make some fries every day!
上面的代码,我使用了python的id函数,它可以把当前传入的变量地址拿出来,然后换成十六进制,就可以捕获它了。
<__main__.Life object at 0x0000013C3294D1C0>
0x13c3283b9d0
Life is to lie down and make some fries every day!
Eat ribs and elbows:
注意看,这里的地址是字符串的地址哦,也就是我们的成员的地址,Python内置的字符串类型并不是原生的,也是经过修饰的字符串类型,它当然也是一个对象了。而这个地址,一定会在我们对象的原始地址中找到。
这里就是我们对象的原地址,我把左边改成了偏移的显示方法,然后我找啊找,眼睛都要瞎了,终于找到了它
这就是我们的第一个成员的地址了,如果我们猜错的话,这个第一个成员的地址是不会发生变化的,都在偏移量为—+140的位置,有兴趣大家可以自己试试。
最后,我来给大家强行修改一下字符串,给加个浪漫的结尾
最后我们给input一个结果,然后打印出来吧
<__main__.Life object at 0x0000013C3294D1C0>
0x13c3283b9d0
Life is to lie down and make some fries every day!
Eat ribs and elbows:f
Life is to lie down and make some fries every day with you!
这个思路其实就是做破解和游戏逆向分析的思路,有兴趣大家可以自己试试。经过了这一篇文章,我相信你一定知道了两个问题的答案,首先,self到底是什么?最后,什么时候使用self参数。