python语言第九天笔记

1399-温同学

发表文章数:72

热门标签

,
首页 » Python » 正文

面向对象(下)

1 继承与重写

  继承是一项代码复用技术,就是新建一个类的时候,使其能够保留已有类的属性和方法。Python 支持多重继承,一个子类可以继承多个父类。
  重写是子类中的属性名或方法名与父类一致,使得父类的属性或方法被覆盖,调用和访问的时候,只是用子类的属性或方法。
  继承的语法格式如下:

class 子类类名(父类 1[,父类 2...]):
	类体

  如果在类定义中没有指定父类,则默认父类是 object 类。也就是说,object 是所有类的父类,里面定义了一些所有类共有的默认实现,比如__new__()。
例:

class Person:				# 没有说继承什么类,那么默认Person继承的是object类

    def __init__(self,name,age):
        self.name = name
        self.__age = age    # 私有属性

    def say_age(self):
        print("年龄,年龄,我也不知道")


class Student(Person):

    def __init__(self,name,age,score):
        Person.__init__(self,name,age)
        # 必须显式的调用父类初始化方法,不然解释器不会去调用,如果不调父类的构造器,也不会报错
        self.score = score


# Student-->Person-->object类
print(Student.mro())            # 打印继承层次结构

s = Student("高淇",18,60)
s.say_age()
print(s.name)
print(dir(s))                   # 查看 s 中的所有属性与方法
# print(s.__age)                # 这句会报错,在父类中是私有属性,在被继承的子类中也是私有
print(s._Person__age)           # 访问父类的私有属性

输出

[<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>]
年龄,年龄,我也不知道
高淇
['_Person__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_age', 'score']
18

  从上面的程序可以看到,子类继承父类的私有属性,私有属性的名称前面,加的类名是父类的名,也就是说,子类得到的私有属性是_Person__age,而非_Student__age。
  子类在继承父类的时候,**如果希望使用父类的属性,那么必须调用父类的构造函数对父类的属性进行初始化。**当然,如果不想使用父类的属性,那么可以不调用父类的构造方法,这跟C++区别很大。父类的属性,子类要么全收,要么一个都不收。
  没有调用父类的构造方法,也可以使用父类的其他函数,前提是这些函数要么不涉及父类的属性,要么涉及的属性已被子类覆盖(同名重写),如

class Person:

    def __init__(self,name,age):
        self.name = name
        self.__age = age    # 私有属性

    def say_age(self):
        print("年龄", self.__age)

    def say_name(self):
        print("姓名", self.name)


class Student(Person):

    def __init__(self,name,age,score):
    	# 没有调用父类的构造函数,不想使用父类的属性
        self.name = name        
        # 这里的 name 不是继承自Person,而是Student自己又定义了一个,把Person中的name属性给覆盖了
        self.score = score


s = Student("高淇",18,60)
s.say_name()        # 虽然没有给父类的name属性初始化,但是子类有name属性,仍然可以调用say_name()方法
# s.say_age()       # 这里会报错,因为子类的构造函数中没有调用父类的构造函数
print(dir(s))

输出

姓名 高淇
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_age', 'say_name', 'score']

  如果子类使用了父类的构造方法,那么子类中,也无法直接访问父类的私有属性,必须用“_父类名__属性”的方式使用父类的私有属性,否则报错

class Person:

    def __init__(self,name,age):
        self.name = name
        self.__age = age    # 私有属性


class Student(Person):

    def __init__(self,name,age,score):
        Person.__init__(self,name,age)
        self.score = score

    def say_age(self):
        print(self._Person__age)        # 如果括号中写成“self.__age”,则会报错


s = Student("高淇",18,60)
s.say_age()

输出

18

继承总结:
  (1)父类的属性,子类要么全收,要么一个都不收,全收的话必须调用父类的构造方法;
  (2)子类中可以不使用父类的构造函数,但仍可以访问父类的其他某些方法(要么不涉及父类的属性,要么涉及的父类属性被覆盖);
  (3)无论是属性还是方法,无论私有还是公有,只要同名就覆盖;
  (4)在子类中使用父类的私有属性和私有方法的方式,与类外访问无区别。

2 打印类的层次结构示意图

  通过类的方法 mro()或者类的属性__mro__可以输出这个类的继承层次结构。

class A:pass
class B(A):pass
class C(B):pass


print(C.mro())
print('#'*30)
print(C.__mro__)

输出

[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
##############################
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

3 object根类

  object 类是所有类的父类,因此所有的类都有 object 类的属性和方法。
  可以使用内置函数查看object的所有属性和方法

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say_age(self):
        print(self.name, "的年龄是:", self.age)


obj = object()
print(dir(obj))
s2 = Person("高淇",18)
print(dir(s2))

python语言第九天笔记
从上面我们可以发现这样几个要点:
  1.Person 对象增加了六个属性:
dictmoduleweakref,age,name,say_age
  2.object 的所有属性,Person 类作为 object 的子类,显然包含了所有的属性。
  3.我们打印 age、name、say_age,发现 say_age 虽然是方法,实际上也是属性。只不过这个属性的类型是“method”而已。
    age <class ‘int’>
    name <class ‘str’>
    say_age <class ‘method’>

4 重写__str__()方法

  object 有一个__str__()方法,用于返回一个对于“对象的描述”。
  假如 p 是 Person的一个对象,那么print§就默认调用了__str__()方法,即print(p.str())

class Person:       #默认继承object类

    def __init__(self,name):
        self.name = name

p = Person("高淇")
print(p)
print(p.__str__())

输出

<__main__.Person object at 0x0000000002605490>
<__main__.Person object at 0x0000000002605490>

str()可以重写

class Person:       #默认继承object类

    def __init__(self,name):
        self.name = name

    def __str__(self):
        return "名字是:{0}".format(self.name)

p = Person("高淇")
print(p)

输出

名字是:高淇

5 多继承

  Python 支持多重继承,一个子类可以有多个“直接父类”,但是由于,这样会被“类的整体层次”搞的异常复杂,尽量避免使用。

6 子类中调用父类方法

  可以用super().方法名,也可以用 父类名.方法名,两者效果等价。

#测试super(),代表父类的定义,而不是父类的对象

class A:

    def say(self):
        print("A:",self)


class B(A):

    def say(self):
        super().say()
        A.say(self)     # 类外就不能这么写了,类外必须通过对象调用
        print("B:",self)


B().say()       
# 建立了一个B类对象,这个对象没有名字,是匿名对象,通过匿名对象调用B类的方法

输出

A: <__main__.B object at 0x00000000025F5FD0>
A: <__main__.B object at 0x00000000025F5FD0>
B: <__main__.B object at 0x00000000025F5FD0>

7 多态

  多态(polymorphism)是指同一个方法调用由于对象不同可能会产生不同的行为。
  例如,同样是吃饭的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭。

class Man:
    def eat(self):
        print("饿了,吃饭啦!")

class Chinese(Man):
    def eat(self):
        print("中国人用筷子吃饭")

class English(Man):
    def eat(self):
        print("英国人用叉子吃饭")

class Indian(Man):
    def eat(self):
        print("印度人用右手吃饭")


def manEat(m):
    if isinstance(m,Man):       # 只要是 Man 的子类对象,都会返回True
        m.eat()                 # 多态,一个方法调用,根据对象不同调用不同的方法!
    else:
        print("不能吃饭")


manEat(Chinese())               # 参数是匿名对象
manEat(English())

输出

中国人用筷子吃饭
英国人用叉子吃饭

关于多态要注意以下 2 点:
  1.多态是方法的多态,属性没有多态。
  2.多态的存在有 2 个必要条件:继承、方法重写。

8 运算符重载

  python语言同C++语言一样,具有运算符重载的功能,所谓的运算符重载,其实就是扩展运算符的功能。python解释器遇到运算符的时候,都是调用函数,比如遇到加号(+)时,其实是调用__add__(),我们在学习字符串的时候说过,加号还有字符串拼接的功能,其实是因为__add__被重载了,使其能够处理两个操作数都是字符串的情况。
  下面是重载加号(+)和乘号(*)的例子

class Person:
    def __init__(self,name):
        self.name = name

    def __add__(self, other):
        if isinstance(other,Person):
            return "{0}--{1}".format(self.name,other.name)
        else:
            return "不是同类对象,不能相加"

    def __mul__(self, other):
        if isinstance(other,int):
            return  self.name*other
        else:
            return "不是同类对象,不能相乘"

p1 = Person("高淇")
p2 = Person("高希希")

x = p1 + p2
print(x)

print(p1*3)

输出

高淇--高希希
高淇高淇高淇

9 特殊属性

  Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊用法,这里我们仅作简要介绍。

#测试特殊属性
class A:
    pass

class B:
    pass

class C(B,A):

    def __init__(self,nn):
        self.nn = nn

    def cc(self):
        print("cc")

c = C(3)

print(dir(c))           # 打印 c 的所有属性和方法
print(c.__dict__)       # 打印子弟你的属性和方法
print(c.__class__)      # 打印对象 c 所属类型
print(C.__bases__)      # 打印 C 类的基类所有组成的元组(多继承)
print(C.__mro__)        # 打印 C 类的层次结构
print(A.__subclasses__())       # 打印由 A 的子类构成的列表

输出

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'cc', 'nn']
{'nn': 3}
<class '__main__.C'>
(<class '__main__.B'>, <class '__main__.A'>)
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
[<class '__main__.C'>]

10 对象的浅拷贝与深拷贝

  当实例对象的属性是某个其他对象的引用时,浅拷贝就是拷贝一个新的对象,但是不拷贝属性所指向的对象,换汤不换药,深拷贝是连属性所指向的对象一起拷贝(如果有多层子对象,则是递归复制),连汤带药一起换。

import copy


class MobilePhone:
    def __init__(self,cpu,screen):
        self.cpu = cpu
        self.screen = screen


class CPU:
    def calculate(self):
        print("算你个12345")
        print("cpu对象:",self)


class Screen:
    def show(self):
        print("显示一个好看的画面,亮瞎你的钛合金大眼")
        print("screen对象:",self)


# 测试变量赋值
c1 = CPU()
c2 = c1
print(c1)
print(c2)
print("测试浅复制....")

# 测试浅复制
s1 = Screen()
m1 = MobilePhone(c1,s1)
m2 = copy.copy(m1)
print(m1,m1.cpu,m1.screen)
print(m2,m2.cpu,m2.screen)

# 测试深复制
print("测试深复制....")
m3 = copy.deepcopy(m1)
print(m1,m1.cpu,m1.screen)
print(m3,m3.cpu,m3.screen)

输出

<__main__.CPU object at 0x00000000025F5FD0>
<__main__.CPU object at 0x00000000025F5FD0>
测试浅复制....
<__main__.MobilePhone object at 0x0000000002087160> <__main__.CPU object at 0x00000000025F5FD0> <__main__.Screen object at 0x00000000020876A0>
<__main__.MobilePhone object at 0x0000000002602850> <__main__.CPU object at 0x00000000025F5FD0> <__main__.Screen object at 0x00000000020876A0>
测试深复制....
<__main__.MobilePhone object at 0x0000000002087160> <__main__.CPU object at 0x00000000025F5FD0> <__main__.Screen object at 0x00000000020876A0>
<__main__.MobilePhone object at 0x00000000025D9580> <__main__.CPU object at 0x0000000002643BE0> <__main__.Screen object at 0x0000000002643C10>

浅拷贝的话,两部手机共用CPU和屏幕,深拷贝的话,两部手机有各自的CPU和屏幕。

11 组合

  与继承类似,组合(“has-a”关系)也是一种代码复用技术,我们可以使用“组合”,也能实现一个类拥有另一个类的方法和属性。

#使用继承实现代码的复用
class A1:

    def say_a1(self):
        print("a1,a1,a1")

class B1(A1):
    pass

b1 = B1()
b1.say_a1()


#同样的效果,使用组合实现代码的复用
class A2:
    def say_a2(self):
        print("a2,a2,a2")

class B2:
    def __init__(self,a):
        self.a = a

a2 = A2()
b2 = B2(a2)
b2.a.say_a2()

输出

a1,a1,a1
a2,a2,a2

  前面的手机例子,其实也是组合,CPU和屏幕作为手机的属性,也可以通过手机对象,间接调用CPU和屏幕的方法和属性,可以自己把程序修改然后跑一下试试。

12 设计模式

  设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法。设计模式有很多种,对于初学者,我们学习两个最常用的模式:工厂模式和单例模式。

(1)工厂模式

  工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制。

class CarFactory:
    def create_car(self,brand):     # 在工厂里造车
        if brand =="奔驰":
            return Benz()
        elif brand =="宝马":
            return BMW()
        elif brand == "比亚迪":
            return BYD()
        else:
            return "未知品牌,无法创建"

class Benz:
    pass

class BMW:
    pass

class BYD:
    pass

factory = CarFactory()
c1 = factory.create_car("奔驰")
c2 = factory.create_car("比亚迪")
print(c1)
print(c2)

输出

<__main__.Benz object at 0x0000000002655FD0>
<__main__.BYD object at 0x000000000262F190>

(2)单例模式

  单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一个访问该实例的全局访问点。
  单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个“单例对象”,然后永久驻留内存中,从而极大的降低开销。
  单例模式有多种实现的方式,仅介绍重写__new__()和__init__()的方法。

class MySingleton:

    __obj = None           # 类属性,用来指向创建好的单例
    __init_flag = True      # 检测参数能否初始化

    def __new__(cls, *args, **kwargs):  # 重写__new__()方法
        if cls.__obj ==None:            # 当__obj为空的时候,表示没有创建单例,可以进行创建
            cls.__obj = object.__new__(cls)

        return cls.__obj         # 如果是第二次创建,这里返回的将是第一次创建对象的指针

    def __init__(self,name):            # 重写__init__()方法
        if MySingleton.__init_flag:
            print("init.....")
            self.name = name
            MySingleton.__init_flag = False
            # 因为单例只能由一个例子,因此初始化方法被调用之后,必须修改标志,防止参数继续被初始化


a = MySingleton("aa")
b = MySingleton("bb")
# 这里__new__方法和__init__方法都被调用了两次,但是参数 name 仅仅被初始化一次,因此最终只生成一个对象
# 换句话说,创建了两个对象,但只有第一个被初始化,而且第二次创建时,返回的也是第一个对象的指针
# 因此第二次创建得到的对象,会马上被垃圾回收器回收
print(a)
print(b)
c = MySingleton("cc")
print(c)

输出

init.....
<__main__.MySingleton object at 0x00000000025D5FD0>
<__main__.MySingleton object at 0x00000000025D5FD0>
<__main__.MySingleton object at 0x00000000025D5FD0>

  可以看到,上面的程序段试图三次创建对象,但是第二次和第三次得到的对象和第一次的完全一样,其原因是重写了__new__方法,使得不管是第几次创建,返回的都是第一次创建的引用。

未经允许不得转载:作者:1399-温同学, 转载或复制请以 超链接形式 并注明出处 拜师资源博客
原文地址:《python语言第九天笔记》 发布于2020-11-17

分享到:
赞(1) 打赏

评论 抢沙发

评论前必须登录!

  注册



长按图片转发给朋友

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

Vieu3.3主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录