python编程基础(五): 面向对象--封装、继承

目录

 1. 模块Module

1.1 Module concept

1.1.1 简单地导入自己写的.py文件

1.1.2 将自己写的多个.py文件规范化成外部类,并创建__init__.py

1.1.3 将自己的程序封装成外部包

2. 面向对象

2.1 类和实例

2.1.1 定义类名

2.1.2 定义__init__函数

2.2 数据封装

2.2.1 class定义一个方法

2.2.3 class summary

2.2.4 访问限制

2.3 继承

2.3.1 子类可以增加一些方法

2.3.2 子类可以重写父类方法 

2.4 多态

2.5 获取对象信息、属性

2.5.1 type()判断对象类型

2.5.2 判断两个变量的type类型是否相同

2.5.3 使用isinstance()判断class类型

2.5.4 dir()函数获取对象所有属性和方法

2.5.5 实例属性和类属性

3. 面向对象高级特性

3.1 __slots__属性 

3.2 使用@property

3.3 多重继承

3.4 定制类

3.5 枚举类

3.6 使用元类

参考


 1. 模块Module

1.1 Module concept

python Module 模块

模块的作用在于提高代码的可维护性,避免函数名和变量名冲突。

一个abc.py文件就是一个abc模块

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
    
    print('Too many arguments!')

if __name__=='__main__':
    test()

第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;

第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

以上就是Python模块的标准文件模板


模块中的函数function

在一个模块中,我们会定义许多函数和变量,但有的变量和函数我们希望给别人使用,有的函数我们希望仅仅在函数内部使用。在python中,这是通过下划线前缀(_)来实现的

  • 正常的函数和变量名是公开的(public),可以直接引用,e.g. abc, x123,
  • 类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__, __name__就是特殊变量。
  • 类似_xxx和__xxx这样的变量就是非公开的(private),不应该被直接引用,e.g. _abc, __abc。private函数和变量不应该被直接引用,而不是不能被直接引用,只是不应该被别人引用。
def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)

这个模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了。这样,调用greeting()函数不用关心内部private函数细节,这也是一种非常有用的代码封装和抽象方法。


python package

为了避免abc模块名与其他模块名冲突,引入一个顶层包名package,比如:mycompany,并按照如下目录存放。

mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py

引入了包以后,只要顶层包名不与其他包冲突,那所有模块都不会和别人冲突。比如,引入包后,abc.py模块名字变成了mycompany.abc。

Notice:每一个包目录下面都会有一个__init__.py文件,这个文件是必须存在的。否则,python就把这个目录当成了普通目录,而不是一个package。__init__.py可以是空文件,也可以有python代码,__init__.py本身就是一个模块,而它的模块名就是mycompany

类似的,可以有多级目录,组成多级层次的包结构,比如:

mycompany
 ├─ web
 │  ├─ __init__.py
 │  ├─ utils.py
 │  └─ www.py
 ├─ __init__.py
 ├─ abc.py
 └─ utils.py

mycompany.utils和mycompany.web.utils两个模块名。

1.1.1 简单地导入自己写的.py文件

将a.py与b.py放在同一项目路径下,然后在另一个b.py文件中执行import a.py,然后我们就可以在b.py中调用a.py中的函数了

import a

a.py()

参考:https://jingyan.baidu.com/article/08b6a591810daf14a8092204.html

1.1.2 将自己写的多个.py文件规范化成外部类,并创建__init__.py

参考:python_baike_spider/baike_spider at master · fifths/python_baike_spider · GitHub

1.1.3 将自己的程序封装成外部包

参考:

Python实现封装打包自己写的代码,被python import_Python_脚本之家python如何将自己写的代码打包供他人使用 - smileyes - 博客园Python将自己写的模块进行打包 - 学一点也是好 - 博客园

2. 面向对象

面向对象,即java的编程思想,与原有的面向过程编程思想不同。一个数据类型抽象化创建为对象(class),这个对象拥有nameproperty两种属性,面向对象思想编程有三大特点:

  • 数据封装。在于复用
  • 继承
  • 多态

2.1 类和实例

面向对象的设计思想是从自然界来的,因为在自然界中,类(class)和实例(instance)的概念是很自然的。比如:鸟class,可以实例化一个对象instance,这个实例有fly()方法属性、size和color颜色属性。

所以,面向对象的设计思想是抽象出class,根据class创建instance。

  • 面向对象的抽象程度比函数要高,因为一个class既包含数据,又包含操作数据的方法。
  • 每个由class创建的具体实例instance,都拥有相同的方法,但各自的数据可能不同。
class Student(object):

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

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

2.1.1 定义类名

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧着着是(object),表示该类从哪个类继承下来。如果没有合适的继承类,就使用object,这是所有类最终都会继承的类。

定义好了Student类,就可以根据Student类创建出Student实例,创建instance是通过类名+()实现的。

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__(分别有两根下划线)方法,在创建实例的时候,就把name, score等属性绑上去

class Student(object):

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

2.1.2 定义__init__函数

Notice:这里的__init__是初始化函数,不同于上面package __init__初始化模块!!! 

注意到__init__方法的第一个参数永远是self,表示创建的实例本身。因此,在__init__方法内部,就可以把各种属性绑定到self,因此self就指向创建的实例本身。 

__init__()初始化函数,用于声明初始化变量、函数functions,深度学习子层layer和模型model

有了__init__方法,在创建实例的时候,就不能传入空的参数了!!必须传入与__init__方法匹配的参数,但self不需要传,python interpreter自己会把实例变量传进去:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

2.2 数据封装

面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例都拥有各自的name和score数据。

虽然,我们可以定义一个函数来访问这些数据,比如打印一个学生的成绩:

>>> def print_score(std):
...     print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59

 但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样就把“数据”给封装起来了

class Student(object):

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

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

2.2.1 class定义一个方法

要定义一个方法,除了第一个参数是self外,其他和普通函数一样。 

要调用这个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入。

>>> bart.print_score()
Bart Simpson: 59

这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑都被“封装”起来了,调用很容易,但却不用知道内部实现的细节 

2.2.3 class summary

类是创建实例的模板,而实例是一个一个具体的对象,各个实例拥有的数据都相互独立,互不影响。

方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据!

通过在实例上调用方法,我们就直接操作了对象内部的数据,但无序知道方法内部实现的细节。

和静态语言不同,python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同

>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

2.2.4 访问限制

在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

但是,从前面的Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性。

>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,就实例instance变成一个私有变量(private),只有内部可以访问,外部不能访问。

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法

class Student(object):
    ...

    def set_score(self, score):
        self.__score = score

通过函数修改,而不是修改__init__初始化变量的做法,其好处是可以对参数做检查,避免传入无效参数。

2.3 继承

在面向对象的程序设计中,当我们定义一个class时,可以从某个现有的class继承,新的class称为子类(Subclass),被继承的class称为基类、父类(Base class、Super class).

继承最大的好处就是子类获得了父类的全部功能。

class Animal(object):
    def run(self):
        print('Animal is running...')

dog = Dog()
dog.run()

cat = Cat()
cat.run()
'''
Animal is running...
Animal is running...
'''

2.3.1 子类可以增加一些方法

class Dog(Animal):

    def run(self):
        print('Dog is running...')

    def eat(self):
        print('Eating meat...')

2.3.2 子类可以重写父类方法 

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run() 

class Dog(Animal):

    def run(self):
        print('Dog is running...')

class Cat(Animal):

    def run(self):
        print('Cat is running...')

2.4 多态

多态是继承的另一种表现形式

a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

>>> isinstance(c, Animal)
True

c不仅仅是Dog,还是Animal。

def run_twice(animal):
    animal.run()
    animal.run()

>>> run_twice(Animal())
Animal is running...
Animal is running...

>>> run_twice(Dog())
Dog is running...
Dog is running...

允许新增animal子类,不必对run_twice()做任何修改。实际上,任何依赖animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态好处即在于,只要是父类的子类,就是可以按照父类进行操作,自动调用实际类型的run()方法。

2.5 获取对象信息、属性

2.5.1 type()判断对象类型

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

# 甚至可以判断一个变量指向函数或者class
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

2.5.2 判断两个变量的type类型是否相同

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True

2.5.3 使用isinstance()判断class类型

我们回顾上次的例子,如果继承关系是:object->Animal->Dog->Husky

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

>>> isinstance(h, Husky)
True

isinstance()函数判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

2.5.4 dir()函数获取对象所有属性和方法

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在python中都是有特殊用途的,比如__len__方法返回长度。实际上,在len()函数内部,它自动去调用该对象的__len__()方法。

仅仅把属性和方法列出来是不够的,配合getattr()、setattr()、hasattr()方法,我们可以直接操作一个对象的状态

>>> obj = MyObject()
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

2.5.5 实例属性和类属性

  • 实例属性

由于python是动态语言

根据类创建的实例可以任意绑定属性,给实例绑定属性的方法是通过实例变量,或者通过self变量

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

s = Student('Bob')
s.score = 90
  • 类属性

给class绑定一个属性,可以直接在class中定义属性,这种属性就是类属性,归Student类所有。

class Student(object):
    name = 'Student'
  • attributes summary
>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

3. 面向对象高级特性

3.1 __slots__属性 

正常情况下,当我们定义了一个类class,创建了一个class的实例后,我么可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。

class Student(object):
    pass
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael

但是,如果我们想限制实例的属性怎么办?比如,只允许Student实例添加name和age属性。

为了达到限制的目的,python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

注意:__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

3.2 使用@property

装饰器(decorator)可以给函数动态加上功能。

对于类的方法,python内置的@property装饰器就是负责把一个方法变成属性调用的。

3.3 多重继承

如果需要混入额外的功能,通过多重继承就可以实现,这种设计称为MixIn。

把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,还可以定义出肉食动物(CarnivorousMixIn)和植食动物(HerbiboresMixIn),让某个动物同时同游好几个MinIn。

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

3.4 定制类

类似__slots__这种形如__xxx__的变量或者函数名,这些在python中是有特殊用途的.

python的class中还有很多有特殊用途的函数,可以帮我们定制类,比如

  • __str__
  • __iter__
  • __getitem__
  • __getattr__
  • __call__

3.5 枚举类

为枚举类型定义一个class类,然后,每个常量都是class的一个唯一实例。

python提供了Enum类来实现这个功能。

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:

3.6 使用元类

  • type(),既可以返回一个对象的类型,又可以创建出新的类型。
  • metaclass()元类,先定义metaclass,然后创建类 --> 控制类的创建行为。

参考

面向对象编程 - 廖雪峰的官方网站

类和实例 - 廖雪峰的官方网站

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天狼啸月1990

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值