python学习笔记:继承(同名问题)、公有与私有、多态、属性与方法、异常(二)

一、继承

1.1、子类重写父类同名属性和方法

class Master(object):
    def __init__(self):
        self.kongfu = "古法"

    def make_cake(self):
        print("按照%s制作一份" % self.kongfu)


class School(object):
    def __init__(self):
        self.kongfu = "现代"

    def make_cake(self):
        print("按照%s制作一份" % self.kongfu)

class Prentice(School, Master):
    def __init__(self):
        self.kongfu = "自创"

    def make_cake(self):
        print("按照%s制作一份" % self.kongfu)
        
cat = Prentice()
print(cat.kongfu)
cat.make_cake()

print(Prentice.__mro__)

"""
运行结果为:
自创
按照自创制作一份
(<class '__main__.Prentice'>, <class '__main__.School'>, <class '__main__.Master'>, <class 'object'>)
"""

所以,子类和父类有同名属性,则默认使用子类。子类的魔法属性__mro__决定了属性和方法的查找顺序。

1.2、子类调用父类同名属性和方法

调用父类方法格式一:父类类名.父类方法(self)
调用父类方法格式二:super(前一个类名, self).父类方法()

这些方式是在子类中的类定义时,创建函数,而函数中调用父类函数,当使用时,调用子类中定义的函数。具体写法如下:

class A(object):
	def show(self):
		...
class B(A):
	def show(self):
		...
	def show_old(self):
		A.show(self)
		super(C, self).show()

a = A()
a.show()      # 调用子类的show
a.show_old()  # 调用父类的show

这种用法与C++中的方式不同:C++可以在使用是直接调用父类函数,而不同通过调用子类的函数间接调用父类的同名函数。

class A{
    
    
	void func(void){
    
    }
};
class B:public A{
    
    
	void func(void){
    
    }
};
int main(void)
{
    
    
	B b;
	b.func();//调用类B中的func
	b.A::func();//调用类A中的func
}

1.2.1、方式一

class Master(object):
    def __init__(self):
        self.kongfu = "古法"

    def make_cake(self):
        print("Masrer 按照<%s>制作一份" % self.kongfu)


class School(object):
    def __init__(self):
        self.kongfu = "现代"

    def make_cake(self):
        print("School 按照<%s>制作一份" % self.kongfu)

class Prentice(School, Master):
    def __init__(self):
        self.kongfu = "自创"

    def make_cake(self):
        print("Prentice 按照<%s>制作一份" % self.kongfu)

    def make_old_cake(self):
        Master.make_cake(self) # self必须要加


cat = Prentice()
print(cat.kongfu)
cat.make_cake()

cat.make_old_cake()

从上述代码中可以发现:

  1. 调用父类函数(方法)时,必须要加self。
  2. 父类的同名属性还是保持子类的同名属性。

1.2.2、方式二

针对方式一的第二个问题,能不能将子类的同名属性也同步到父类?
通过执行父类的__init__方法,来修改self的属性值。(其实不一定是__init__方法,可以自己定义修改的属性值的函数)

class Master(object):
    def __init__(self):
        self.kongfu = "古法"

    def make_cake(self):
        print("Masrer 按照<%s>制作一份" % self.kongfu)


class School(object):
    def __init__(self):
        self.kongfu = "现代"

    def make_cake(self):
        print("School 按照<%s>制作一份" % self.kongfu)

class Prentice(School, Master):
    def __init__(self):
        self.kongfu = "自创"

    def make_cake(self):
        print("Prentice 按照<%s>制作一份" % self.kongfu)

    def test(self):
        print("test")

    def make_old_cake(self):
        Master.__init__(self)
        Master.make_cake(self)


cat = Prentice()
print(cat.kongfu)
cat.make_cake()

print("-"*20)
cat.make_old_cake()
cat.make_cake()

"""
运行结果为:
自创
Prentice 按照<自创>制作一份
--------------------
Masrer 按照<古法>制作一份
Prentice 按照<古法>制作一份
"""

super函数的用法说明:

  1. super().父类函数(),它会默认多继承中从左到右的顺序来调用。
  2. super(C, self).父类函数(),它会调用多继承中C类的后一个类的函数。如果C类是子类,则调用多继承中的左边第一个。

1.3、多层继承

class Master(object):
    def __init__(self):
        self.kongfu = "古法"

    def make_cake(self):
        print("Maser 按照%s制作一份" % self.kongfu)


class School(object):
    def __init__(self):
        self.kongfu = "现代"

    def make_cake(self):
        print("School 按照%s制作一份" % self.kongfu)

class Prentice(School, Master):
    def __init__(self):
        self.kongfu = "自创"

    def make_cake(self):
        print("Prentice 按照%s制作一份" % self.kongfu)

    def make_old_cake(self):

        # 调用格式 : 父类类名.父类方法(self)
        print("执行Master类的__init__之前self.kongfu是什么 %s" % self.kongfu)
        Master.__init__(self)
        print("执行Master类的__init__之后self.kongfu是什么 %s" % self.kongfu)
        Master.make_cake(self)

    def make_new_cake(self):
        # 调用格式 : 父类类名.父类方法(self)
        print("执行School类的__init__之前self.kongfu是什么 %s" % self.kongfu)
        School.__init__(self) # 必须要,不然无法调用到父类的同名属性方法
        print("执行School类的__init__之后self.kongfu是什么 %s" % self.kongfu)
        School.make_cake(self)


class PrenticePrentice(Prentice):
    pass

cat = PrenticePrentice()
cat.make_cake()       # 父类实例方法(函数)

print("-"*30)
cat.make_old_cake()  # 通过父类函数调用祖父类函数
print("-"*30)
cat.make_new_cake()  # 通过父类函数调用祖父类函数
print("-"*30)
cat.make_cake()      # 父类实例方法(函数)

二、公有与私有

2.1、公有属性和公有方法

类的属性
命名格式:由字母或_开头,总体由字母、数字和_构成
使用方法:在类外面 :对象名.属性名 在类内部 : self.属性名

类的方法
使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数
命名格式:由字母或_开头,总体由字母、数字和_构成
使用方法:在类外面 :对象名.方法名(实参表) 在类内部 : self.方法名(实参表)类名.方法名(self, 实参表)——同名或不同均可使用

前面的示例中,类里定义的属性和方法为公用的。

2.2、私有属性和私有方法

Python中以属性命名方式来区分,如果在属性名前面加了2个下划线’__’,则表明该属性是私有属性,否则为公有属性(方法也是一样,方法名前面加了2个下划线的话表示该方法是私有的,否则为公有的)。

类的私有属性
命名格式:__private_attrs 由__开头,总体由字母、数字和_构成
使用方法: 只在类内部 : self.__private_attrs

类的私有方法
命名格式:__private_function 由__开头,总体由字母、数字和_构成
使用方法:只在类内部 : self.__private_function(实参表)

示例:

class People(object):
    def __init__(self, name):
        self.__name = name

    def getName(self):
        return self.__name

class stu(People):
	def __init(self, name):
		self.__stu_name = name
	def show(self):
		pass
		# print(self.__name) 'stu' object has no attribute '_stu__name'

people = People("xiaoming")
# print(people.__name)  # 'People' object has no attribute '__name'
print(people.getName())

"""
运行结果为:
xiaoming
"""

1、私有属性和方法只能在类定义时使用,在对象是无法使用私有属性和方法的。(与C++的私有成员相似)
2、子类无法使用父类的私有属性和方法,需要通过公用方法间接使用。(与C++的继承性质相似)

三、多态

定义时的类型和运行时的类型不一样,此时就成为多态。
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型 ”。

鸭子类型:
虽然我想要一只"鸭子",但是你给了我一只鸟。 但是只要这只鸟走路像鸭子,叫起来像鸭子,游泳也像鸭子,我就认为这是鸭子。

class F1(object):
    def show(self):
        print("F1.show")

class S1(F1):
    def show(self):
        print("S1.show")

class S2(F1):
    def show(self):
        print("S2.show")

def func(obj):
    obj.show()

s1 = S1()
func(s1)

s2 = S2()
func(s2)

"""
运行结果为:
S1.show
S2.show
"""

四、属性与方法

区分概念:类属性、实例属性、类方法、实例方法、静态方法、类对象cls、实例对象self

4.1、类属性

类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问。

对于公有的类属性访问:

  1. 类名.类属性名
  2. 实例对象.类属性名

对于私有的类属性只能在类内被访问。

示例:

class People(object):
    country = "china" # 类属性

a = People()   # 实例对象a

print(a.country)       # 通过实例对象访问公有类属性
print(People.country)  # 通过类名访问公有类属性

"""
运行结果为:
china
china
"""
通过类对象修改类属性的值

通过实例对象.属性名或者self.属性名的方式修改类属性,从代码执行效果上看,确实已经修改成功,但是,事实上并没有对类属性进行修改,只是在当前的实例对象中创建了一个同名的实例属性,并且之后以实例对象引用该名称的属性的方式时(实例对象.属性值),实例属性会强制屏蔽类属性,即引用的是实例属性,除非删除该示例属性。

示例:

class A(object):
    value1 = 11

a = A()
a.value1 = 2

print(a.value1)
print(A.value1)

"""
运行结果为:
2
11
"""

从示例代码中可知,这种方式并没有修改类属性的值!!!

只有通过类对象才能修改类属性的值

示例:

class A(object):
    value1 = 11

a = A()
A.value1 = 2

print(a.value1)
print(A.value1)

"""
运行结果为:
2
2
"""

4.2、实例属性

定义:一个类的实例对象所拥有的属性
特点:独立的,一对一
拥有者:每个对象都拥有自己的实例属性

创建实例属性的方式:在类中通过 self.属性名 或者 实例对象.属性名

第二种方式,只是在当前的实例对象中仅有的,如果想在所有示例对象中有该属性,需要在__init__初始化方法中定义该示例属性。

示例:

class A(object):
    def __init__(self):
        self.value1 = 1

a = A()
a.value2 = 2

print(a.value1)
print(a.value2)

"""
运行结果为:
1
2
"""

4.3、类方法

类方法:是类对象所拥有的方法。
特点:@classmethod 在上面修饰 ,类方法第一个参数必须是类对象。(与实例方法相似,第一个参数必须是实例对象)。
应用:访问类属性,并且可以对类属性进行修改。

语法格式:

@classmethod
def 类方法名(cls):
	执行代码

对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以’cls’作为第一个参数的名字,就最好用’cls’了)

访问格式:

类名.类方法名(实参列表)
实例对象名.类方法名(实参列表)

示例:

class A(object):
    v1 = 0
    def __init__(self):
        self.v2 = 1
    @classmethod
    def func(cls, self):
    
        cls.v1 = 2   # 类属性
        self.v2 = 3  # 实例属性
        
        print(cls.v1)
        print(self.v2)


a = A()

a.func(a)   # 实例对象名.类方法名(实参列表)
A.func(a)   # 类名.类方法名(实参列表)

"""
运行结果为:
2
3
2
3
"""

4.4、实例方法

类方法去掉前面的修饰器@classmethod就是实例方法,实例方法的第一个参数是实例对象self

4.5、静态方法

静态方法:是类对象所拥有的方法
特点:@staticmethod来进行修饰,不需要多定义参数。(添加参数好像也可以)
应用:访问类属性,并且可以对类属性进行修改

语法格式:

@staticmethod
def 静态方法名():
	执行代码

访问格式:

类名.静态方法名(实参列表)
实例对象名.静态方法名(实参列表)

示例:

class People(object):
    country = "china" # 类属性

    @staticmethod
    def get_country():
        return People.country

p = People()

# 通过对象来访问静态方法
print(p.get_country())

# 通过类来访问静态方法
print(People.get_country())

"""
运行结果为:
china
china
"""

或者

class A(object):
    v1 = 0
    def __init__(self):
        self.v2 = 1
    @staticmethod
    def func(cls, self):
        cls.v1 = 2      # 类属性
        self.v2 = 3     # 实例属性
        print(cls.v1)
        print(self.v2)


a = A()
a.func(A, a)           # 实例对象名.静态方法名(实参列表)
A.func(A, a)           # 类名.静态方法名(实参列表)

"""
运行结果为:
2
3
2
3
"""

4.6、小结 ☆

  1. 类方法第一个参数必须是类对象cls,通过cls引用的必定是类对象的属性和方法,即类属性和类方法。
  2. 实例方法第一个参数必须是实例对象self,通过self引用的可能是实例属性、实例方法,也可能是类属性、类方法。
  3. 如果出现类属性名和实例属性名同名的话,实例对象.属性名的方式进行访问时,实例属性优先级更高。
  4. 如果出现类方法名和实例方法名同名的话,实例对象.方法名的方式进行访问时,类方法优先级更高。
  5. 继承时,父类与子类出现同名属性和方法时,按__mro__属性的查找顺序查找。
类属性 实例属性 类方法 实例方法 静态方法
定义 属性名 = 属性值 self.属性名 = 属性值 方法前加@classmethod,方法的第一参数为类对象cls 方法,方法的第一参数为实例对象self 方法前加@staticmethod,无参数
类内访问 cls.属性名 或 类名.属性名 self.属性名 cls.方法名(实参列表) 或 类名.方法名(实参列表) self.方法名(实参列表) 或 类名.方法名(self, 实参列表) 类名.方法名(实参列表)
类外访问 实例对象名.属性名 或 类名.属性名 实例对象名.属性名 实例对象名.方法名(实参表列) 或 类名.方法名(实参表列) 实例对象名.方法名(实参列表) 实例对象名.方法名(实参列表) 或 类名.方法名(实参列表)

说明:

  • 调用类方法和实例方法时,实参列表不包括类对象和实例对象。
  • 调用静态方法,实参列表包括类对象和实例对象。
  • 类名.方法名(self, 实参列表),在类内访问,解决父子类中方法同名问题。

五、异常

当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常"。

5.1、捕获多个异常——try…except…

try:
    file = open("1.txt")   # 文件不存在
    print(num)             # num属性不存在
except(IOError, NameError): # 捕获IOError和NameError异常
    print("---find error---")

"""
运行结果为:
---find error---
"""

注意: 当发现open出现异常时,直接跳转到except后面的语句进行执行,不会执行print(num)。

5.2、捕获异常并储存

获取一个异常的信息描述,并存储,语法格式:

try:
	可能会出现异常的语句
except 异常类型 as 存储位置变量名:
	pass # 表示直接略过 # 或者执行语句

获取多条异常,并存储,语法格式:

try:
	可能会出现异常的语句
except (异常类型1, 异常类型2,...,异常类型N) as 存储位置变量名:
	pass # 表示直接略过 # 或者执行语句

示例:

try:
    file = open("1.txt")
    print(num)
except(IOError, NameError) as rest:
    print("---find error---")
    print(rest)

print("-"*20)
# print(rest) # rest只能在except内部使用

"""
运行结果为:
---find error---
[Errno 2] No such file or directory: '1.txt'
--------------------
"""

注意: rest只能在except内部使用。

5.3、捕获所有异常

方式一

语法格式:

try:
	可能会出现异常的语句
except:
	pass # 表示直接略过 # 或者执行语句

这种捕获所有异常没有存储,不利于调试代码,一般不推荐使用。

示例:

try:
    file = open("1.txt")
    print(num)
except:
    print("---find error---")

"""
运行结果为:
---find error---
"""
方式二

语法格式:

try:
	可能会出现异常的语句
except Exception as 存储位置变量名:
	pass # 表示直接略过 # 或者执行语句

示例:

try:
    file = open("1.txt")
    print(num)  # 未执行
except Exception as rest:
    print("---find error---")
    print(rest)

"""
运行结果为:
---find error---
[Errno 2] No such file or directory: '1.txt'
"""

5.4、异常的传递

异常传递一般会出现在try嵌套使用时和多函数之间调用,如果try嵌套,那么如果里面的try没有捕获到这个异常,那么外面的try会接收到这个异常,然后进行处理,如果外边的try依然没有捕获到,那么再进行传递。。。

5.4.1、抛出自定义异常——raise语句

raise语句:可以引发一个异常。异常/错误对象必须有一个名字,自定义一个异常类,且它们应是Error或Exception类的子类。

语法格式:


class 自定义异常类名(Error或者Exception):
	# 异常类的方法...
	def __init(self):
		...

抛出异常语句格式:

raise 异常类名(传参列表)

示例:

class shortInput(Exception):
    def __init__(self, length):
        self.length = length

def main():
    try:
        s = input("请输入:")
        if len(s) < 3:
            raise shortInput(2)
    except shortInput as e:
        print(e.length)

main()

"""
输入1, 运行结果为:
2
"""

5.4.2、异常处理中抛出异常

在使用try…except…中,如果出现异常,可以选择捕捉该异常,自己处理;也可以选择抛出该异常,让系统处理(默认处理)

语法格式: 添加一个判断条件,若条件为真,表示捕捉异常自己处理;若为假,可以抛出异常,系统默认处理。

try:
	可能出现异常的语句
except Exception as 存放位置:
	if 条件:
		异常该处理
	else:
		# 重新抛出异常
		raise

六、参考资料

[1] : python多继承中子类调用父类的同名方法
[2] : Python面向对象之私有属性和方法
[3] : python的类属性、实例属性、类方法、静态方法
[4] : 类属性、实例属性;类方法,实例方法,静态方法

猜你喜欢

转载自blog.csdn.net/weixin_42258222/article/details/106892909
今日推荐