Python面向对象编程

构建类

写一个简单的类

class Lion(object):
	def __init__(self,color,sound):
		self.color=color
		self.sound=sound

类名后面打括号加object的意思是它是一个基类,不带参数或者连括号都不带默认也是这个意思,如果它是某个类的子类,那么不仅要带括号,括号中还要写上父类名。

init()方法的作用

你可以将这个方法类比为C++里面的构造函数,实际上它就是一个构造函数。
新建类实例时会被自动调用,为属性赋值,如果不特别声明,它只会被初始化一遍,往后不管将实例使用几遍,__init__()方法不会再被自动调用。

self是用来干什么的

self是__init__()方法的第一个参数,必须存在。它指向类实例的地址,是实例得以存在的原因。当然,如果后面带上__class__,它便会指向当前类。实际上,哪怕__init()__方法的第一个参数不叫self也行,但必须有一个额外参数作为指针存在。譬如我们将上面那个类写成下面这样也行

class Lion(object):
	def __init__(xxx,color,sound):
		xxx.color=color
		xxx.sound=sound

类属性

什么是类属性

那我上面构造的那个类来说,它就有两个属性——color,sound。它们分别代表狮子的颜色和叫声(lion:狮子)。当然,狮子不仅有这两方面的特点,譬如它几岁,它住在哪里,这些都可以作为属性添加。

类有几种,不同种类有什么区别

类比C++,它的属性也有三种

类型 特点 格式
公有属性 允许随意访问 self.name
受保护属性 允许随意访问,但是不建议直接访问 self._name
私有属性 拒绝访问 sel.__name

说明:公有属性以字母开头,受保护属性以单下划线开头,私有属性以双下划线开头。后面讲到的三种类方法也已这种方式定义。
下面我们来看

class Sample(object):

	def __init__(self):
		self.a=1
		self._b=2
		self.__c=3

shili=Sample()
print(shili.a)
print(shili._b)

output

1
2

而这段代码

class Sample(object):
	...
	...
	...
print(shili.__c)

output

1
2
Traceback (most recent call last):
  File "test.py", line 24, in 
    print(shili.__c)
AttributeError: 'Sample' object has no attribute '__c'

可以看出,受保护属性虽不建议直接访问但的确是可以直接访问的,而私有属性压根儿找不着。那么以怎样的方式去使用受保护属性和私有属性呢?在类方法中我会讲到。

类方法

类方法如何定义

对待类方法,我们仍然像对待一般方法那样,使用def关键字进行定义,但形参和一般方法有所区别。就像我们的__init__()方法那样,类方法也需有特定的首位形参,与__init__()方法的相同。
看下面这段代码

class Sample(object):
	...
	...
	...
	def fun(self):
		pass

这即为类方法的定义了。

类方法如何访问属性

类方法访问属性同样须使用self关键字,假如你给__init__()的第一个形参叫做xxx那么就用xxx来访问。总之__init__()方法的第一个参数你不管传什么它都会立刻被提升为关键字。
看下面这段代码

class Sample(object):
	...
	...
	...
	def fun(self):
		print(self.a)

譬如说你想打印a属性,那么应该这样给print()传参。

有哪些特殊的类方法

最特殊的类方法毋庸置疑当然是__init__()。那还有些什么地位比较超然的类方法呢?这里列举一些

方法名 方法释义
_del_ 析构函数,释放对象时使用
_repr_ 打印,转换
_setitem_ 按照索引赋值
_getitem_ 按照索引获取值
_len_ 获得长度
_cmp_ 比较运算
_call_ 函数调用
_add_ 加运算
_sub_ 减运算
_mul_ 乘运算
_truediv_ 除运算
_mod_ 求余运算
_pow_ 乘方

调用类方法

类方法由类实例来调用,类实例通过.来调用类方法
如何产生一个类实例?看下面这段代码

class Sample(object):
	...
	...
	...
shili=Sample()

类实例由此产生,第五行的Sample是否带参数依__init__方法而定。根据上面那个方法,我们是不需要给参数的。
调用类方法访问受保护属性以及私有属性
对于受保护以及私属性,我们一般在类中提供接口进行访问,无论是取值还是修改。看下面这段代码

class Sample(object):
	"""docstring for Sample"""

	def __init__(self):
		...
		self._b=2
		self.__c=3
		...

	def set_b(self,_b):
		self._b=_b

	def get_b(self):
		return self._b

	def set_c(self,__c):
		self.__c=__c

	def get_c(self):
		return self.__c

if __name__=="__main__":
	shili=Sample()
	print(shili.get_b(),shili.get_c())
	shili.set_b(5)
	shili.set_c(6)
	print(shili.get_b(),shili.get_c())

output

2 3
5 6

### 属性分类的意义
根据自己的意愿和需要对属性进行定制

实例属性与类属性

实例属性在__init__方法中定义,类初始化时产生,也就是说它只对实例可见,那我们怎么给类定义属性呢?看这段代码

class Sample(object):
	
	e=0
	_e=1
	__e=2

	def __init__(self):
		...
		...

__init__方法上面初始化的那些变量就是类属性。实力属性与类属性的区别在于,实例属性由实例独享,类属性由这个类的所有实例共享。两类属性的分级和级别所对应的访问限制是一样的。

类继承

单继承

被继承的类叫基类、父类或者超类,也有一种比较小众的叫法:根类。继承而来的类叫做子类。
在写法上与基类上没有太大区别,你只需要注意在类名后面的括号里写上基类名就行了。参考下面写法

class Sample(object):
	#基类
	...
class Son(Sample):
	#子类
	...

如果基类在某个模块当中,假定这个模块叫做Xxx,那么子类这么写

class Son(Xxx.Sample):
	...

在继承一个类的同时,我们也会继承它的属性和方法,对于属性和方法,我们可以选择覆写。进行重写之后再有子类实例调用该方法或属性则直接从子类中调用。见下面代码

class Sample(object):
	"""docstring for Sample"""
	e=0
	...

	def __init__(self):
		...
		self._b=2
		...

	def get_b(self):
		return self._b
	
class Son(Sample):
	e=1

	def get_e(self):
		return self.e
	
if __name__=="__main__":
	shili=Son()
	print(shili.get_e())
	print(shili.get_b())

output

1
2

在上面这段代码中,子类Son覆写了基类的e属性,继承了基类的构造方法以及get_b方法,并由自己定义了get_e方法。调用get_b方法对没有被覆写的_b进行了打印,调用get_e方法打印了被覆写的e属性。可以看出的是,子类不仅能够自由使用父类的方法,还能自由覆写父类的属性。可是不是什么属性都能被覆写呢?请看代码

class Sample(object):
	"""docstring for Sample"""
	e=0
	...

	def __init__(self):
		...
		self._b=2
		...

	def get_b(self):
		return self._b
	
class Son(Sample):
	_b=1

if __name__=="__main__":
	shili=Son()
	print(shili.get_b())

output

2

我们明明已经在子类当中将_b修改成了1,为什么打印出来还是父类中的2呢?请看代码

class Sample(object):
	"""docstring for Sample"""
	e=0
	...

	def __init__(self):
		...
		self._b=2
		...

	def get_b(self):
		return self._b
	
class Son(Sample):
	def __init__(self):
		self._b=1

if __name__=="__main__":
	shili=Son()
	print(shili.get_b())

output

1

在子类中重写了__init__方法之后覆写就生效了。所以,如果你要覆写基类的实例属性,那么同时你也应该覆写__init__方法。因为如果子类里面没有__init__方法,那么Python就会调用父类的__init__方法对实例属性进行初始化,子类实例属性便无法覆盖基类了。


这引发了一个思考,如果覆写的是类属性而不是实例属性,就万事大吉了吗?看代码

class Sample(object):
	"""docstring for Sample"""
	e=0
	_e=1
	__e=2
	...

	def __init__(self):
		...

	def get_1(self):
		return self.e
	
	def get_2(self):
		return self._e
	
	def get_3(self):
		return self.__e
	
class Son(Sample):
	e=4
	_e=5
	__e=6

if __name__=="__main__":
	shili=Son()
	print(shili.get_1())
	print(shili.get_2())
	print(shili.get_3())

output

4
5
2

可见e和_e都被覆写了,但是__e没有,还是那个私有属性的问题,之前说了它不可以在类外被访问,这里再一次验证。


这样一来又引发了一个思考,基类所有方法都可以被覆写吗?看代码

class Sample(object):
	...

	def __init__(self):
		...

	def one(self):
		return "我是公有方法"
	
	def _one(self):
		return "我是受保护方法"
	
	def __one(self):
		return "我是私有方法"
	
class Son(Sample):
	pass

if __name__=="__main__":
	shili=Son()
	print(shili.one())
	print(shili._one())
	print(shili.__one())

output

我是公有方法
我是受保护方法
Traceback (most recent call last):
  File "test.py", line 65, in 
    print(shili.__one())
AttributeError: 'Son' object has no attribute '__one'

看,方法的分类也是对标类属性的,私有方法一样不能让子类访问。可能你要问了,公有方法和受保护方法的效果是一样的啊,二者之间的划分有什么意义呢?原则上,公有方法可以跨模块调用,而受保护方法不可以。但根据枫枫的实操,你要跨模块调用受保护方法也可以。


如果子类实例想要调用父类中某个被覆写的方法怎么办?看代码

class Sample(object):
	...

	def __init__(self):
		...

	def Print(self):
		print("我是父类")
	
class Son(Sample):
	def Print(self):
		print("我是子类")

if __name__=="__main__":
	shili=Son()
	shili.Print()
	super(Son,shili).Print()

output

我是子类
我是父类

super方法可以被用来调用父类。传递子类名以及子类实例就可以访问父类被覆写的方法了。

多继承

多继承不是A->B->C的关系,而是A->C,B->C的关系,即多个父类对应单个子类。形如

class One:
	pass

class Two:
	pass

class Three(One,Two):
	pass

多继承不会直接继承父类中的构造函数,因为可能会有多个构造函数,你需要在子类中进行覆写,如果不想覆盖父类的构造函数,至少要多个构造函数进行整合。示例如下

class One(object):
	def __init__(self,a):
		self.a=a

class Two(object):
	def __init__(self,b):
		self.b=b

class Three(One,Two):
	def __init__(self,a,b):
		One.__init__(self,a)
		Two.__init__(self,b)

	def get_a(self):
		return self.a
	def get_b(self):
		return self.b
if __name__=="__main__":
	shili=Three(1,2)
	print(shili.get_a())
	print(shili.get_b())

output:

1
2

如果多个父类中有同名函数怎么办?默认调用括号中排序靠前的类的方法。示例如下

class One(object):
	def __init__(self,a):
		self.a=a

	def Print(self):
		print("I am One.")

class Two(object):
	def __init__(self,b):
		self.b=b

	def Print(self):
		print("I am Two.")

class Three(One,Two):
	def __init__(self,a,b):
		One.__init__(self,a)
		Two.__init__(self,b)
		...
if __name__=="__main__":
	shili=Three(1,2)
	shili.Print()

output

I am One.

你可能感兴趣的:(Python)