程序设计中“面向对象”是个老生常谈的话题,而Python则把“对象”发挥到了极致!
那究竟什么是“对象”?
我认为,一切可名状或不可名状之物皆可以称之为“对象”!世界是由对象组成的,世界本身也是一个对象。Python的世界秉承的正是这样的理念——一切皆对象——字符、数字、列表、元组、集合、字典、函数、类、模块、某种操作、甚至Python本身,等等都是对象。
在某些编程语言中,“对象”的定义较为苛刻,比如要求对象必须有“属性attribute”和“方法method”,或者对象必须可以子类化。而Python则更为宽容,没有这样那样的限制,它既包含狭义的对象,同时也将其他元素当成对象来对待。
那么Python究竟是怎么通过具体方式来体现“万物皆对象”的逻辑的呢?有没有更直观的理解方式?当然有!答案就是:
“Python, 一切都可以赋值!”
没错,一切都可以赋值!也就是可以执行‘=’操作。变量可以赋值、属性可以赋值、方法可以赋值、函数可以赋值、任何一种操作都可以赋值,即使很多时候这种赋值没有任何实际意义,但依然可以赋值!
举个例子,对于列表a=[1,2,3],a.append(4)表示调用列表方法把4加入到a的末尾作为第四个元素,它纯粹是一种操作,没有返回值。因此,如果写成b=a.append(4),除了诠释一下画蛇添足,并没有其他实际意义。然而神奇的Python告诉你:“没错,你可以这样干,因为我把a.append(4)这一操作过程也当成了对象,既然是对象,在我们Python的世界里那就是可以赋值的。不过我还想告诉你,这样的赋值对于完成你的工作没啥意义,因为这里的b啥也不是,但真的不影响我把他当对象!”。
看下面的代码显得更清楚。可见b的值是‘None’,而类型是‘None Type’,这意味着b没啥实际含义,但即使这样,Python还是一视同仁地给它分配了地址!深深地爱意,有没有?还可以继续赋值c = d,因为都是对象嘛。
>>>a = [1,2,3]
>>>b = a.append(4)
>>>print('a={},type(a):{}'.format(a,type(a)))
>>>print('b={},type(b):{}'.format(b,type(b)))
>>>print('id(b)={}'.format(id(b)))
a=[1, 2, 3, 4],type(a):<class 'list'>
b=None,type(b):<class 'NoneType'>
id(b)=1464837264
“Python,一切皆可以赋值”还有另一种表述:
“Python, 一切皆有“类型(type)”!
关于“类型”,int、string、list、tuple、set、dict、bool这些最基本的就不再啰嗦了,除此以外,还有各种各样稀奇古怪的类型,如前面提到的‘None Type’,对,它也属于一种独立的“类型”(这就好像数字0,虽然啥也没有,但它也是个数字啊)。Python对象都有“类型”,各种第三方库也都有自己各式各样的“类型”,用户甚至可以定义自己的“类型”。
在python中有一个非常有用的内置函数:type(),它可以返回对象的具体类型。
比如type(None),可见关键字‘None’的类型就是‘NoneType’。
>>>type(None)
NoneType
再比如type(type),可见函数type自己就是一个‘type’类型,这有点儿绕啊。
>>>type(type)
type
再比如内置求最大值函数“max”,从下面的代码可以看出函数‘max’本身的类型是builtin_function_or_method;而‘max()’的类型实际表示了返回值的类型;如果直接type(max())则会报错,因为没有输入,自然也无法判断输出是啥类型了。
>>>type(max)
builtin_function_or_method
>>>type(max([1,2,3]))
int
>>>type(max([1.,2.,3.]))
float
>>>type(max())
TypeError: max expected 1 arguments, got 0
注意:
(1) 在利用type()查看函数或方法时,函数名或方法名代表了函数或方法本身,而带上'()'则表示返回值的类型,如前面的type(max)和type(max())。
(2) python的部分关键字,如and、as、break、if、elif等等,确实是没有类型的,连‘NoneType’都不算。输入type(and),将得到错误信息SyntaxError: invalid syntax。可怜,这些关键字作用很大,连个对象都不算!
Python中尽量用狭义的“对象”概念进行编程
虽然Python对“对象”是宽容的,但大部分对象依然具备狭义“对象”的基本特性,即:对象有“属性attribute”和“方法method”!换句话说,操作对象可以通过点操作符“.”进行,即:
“object.attribute”或“object.method()”
还是举实例来直观说明。在MATLAB中对a=-1求绝对值必须用abs(a)的方式进行,因为a不是一个狭义的对象,它本身没有属性和方法,因此只能通过第三方函数进行操作。
在Python中情况就不一样了,a=-1它不仅是一个int整数,它也是一个int类型的对象,有它自己的属性和方法,取绝对值操作,不仅可以利用内置函数abs(),同样可以利用自身方法“__abs__()”,看如下代码,两者结果是完全相同的。
>>>a = -1
>>>print(abs(a))
>>>print(a.__abs__())
1
1
Python中的对象千千万,想要记住每一个对象都有啥属性和方法是不切实际的。但Python有一个非常有用的函数:dir(),它可以返回对象的基本属性和方法。如下所示,三者都输出了相同的结果,都是str对象的属性和方法,可见dir()的参数可以是类名称、变量名、实际对象等。
>>>a = 'ABC'
>>>dir(str)
>>>dir(a)
>>>dir('ABC')
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
……]
体会面向对象操作的魅力!
前面对比了“面向对象操作”与“直接利用内置函数操作”的差异,似乎没展现出“面向对象”的优点!别急,下面的例子一定会让你印象深刻!将a=[1,2,[3,-4]]中的-4取绝对值并转化为字符。
(1)利用第三方函数方法
str(abs(a[2][1]))
a[2]取出a的第三个元素[3,-4];[1]表示取出[3,-4]中的第二元素-4;abs()对-4取绝对值;str()将4转化为字符。
>>>a=[1,2,[3,-4]]
>>>str(abs(a[2][1]))
'4'
(2)利用对象自身方法操作
a[2][1].__abs__().__str__()
首先a[2]取出[3,-4],是list对象;[1]取出-4,是int对象,它有一个__abs__()方法,返回绝对值4(依然是个int对象),int对象还有一个方法是__str__(),它将int转化为str对象,得到字符‘4’。如果想对这个字符‘4’进一步操作,则可以利用dir(str)查看str都还有哪些方法,继续加在后面便可。
>>>a=[1,2,[3,-4]]
>>>a[2][1].__abs__().__str__()
'4'
通过比较可知,利用函数方法会出现层层包含的括号,当多个函数同时作用时,显得较为混乱。而利用“对象方法”则结构清晰,各方法从左到右逐个执行,由‘.’将各个方法分开,这种顺序结构一目了然。此外,内置函数的数量是极有限的,通常是一些通用型函数,对于很多特殊或者专门场景,无法在内置函数中找到需要的函数。通过将方法置于对象内部,既方便使用,也避免了将很多极私有的方法暴露于外部环境中,引起结构混乱。
鉴于Python的面向对象特性,编程时应利用对象的方法进行操作!
注意:
在利用对象方法进行操作时,下一个方法能从哪些方法里面选,是由上一个方法的返回值类型决定的,不能对int对象使用str对象的方法。如下例,a[0]是一个int对象,有__abs__()方法,而a[1]是一个str对象,没有__abs__()方法,会报错:
>>>a = [-1,'1']
>>>a[0].__abs__()
1
>>>a[1].__abs__()
AttributeError: 'str' object has no attribute
下面的例子中,__abs__()可以写无限个,原因在于a是一个int对象,调用自身方法__abs__()后返回的还是int对象,因此可以无限写下去,虽然没什么卵用,但有力展现了面向对象方法的特点!
>>>a= -1
>>>a.__abs__().__abs__().__abs__().__abs__().__abs__()
1