【Python基础教程】 第7章 再谈抽象

             *     ,MMM8&&&.            *
                  MMMM88&&&&&    .
                 MMMM88&&&&&&&
     *           MMM88&&&&&&&&
                 MMM88&&&&&&&&
                 'MMM88&&&&&&'
                   'MMM8&&&'      *    
          |\___/|     /\___/\
          )     (     )    ~( .              '
         =\     /=   =\~    /=
           )===(       ) ~ (
          /     \     /     \
          |     |     ) ~   (
         /       \   /     ~ \
         \       /   \~     ~/
  jy__/\_/\__  _/_/\_/\__~__/_/\_/\_/\_/\__ww
  |  |  |  |( (  |  |  | ))  |  |  |  |  |  |
  |  |  |  | ) ) |  |  |//|  |  |  |  |  |  |
  |  |  |  |(_(  |  |  (( |  |  |  |  |  |  |     
  |  |  |  |  |  |  |  |\)|  |  |  |  |  |  |
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |

在表示输入和输出上,采用了 ‘In - Out‘ 和 ‘>>>‘ 多种表达方式,后面统一采用IPython 中的 ‘>>>‘ 来表示输入 In。



7.1 对象魔法

提到面向对象,总是离不开几个重要的术语:多态(Polymorphism),继承(Inheritance)和封装(Encapsulation)。Python也是一种支持OOP的动态语言,本文将简单阐述Python对面向对象的支持。

7.1.1 多态

可对不同类型的对象执行相同的操作,且这些操作能正常运行。

比如:

def add(x,y):
    return x+y

In :add(1,2)
Out:3

In :add('Love ','you')
Out:'Love you'
#repr时多态的集大成者之一,可用于任何对象
In [1]: def length_message(x):
   ...:     print(repr(x),'的长度为 ',len(x)) 

In [2]: length_message('love')
'love' 的长度为  4

In [3]: length_message(['df',12])
['df', 12] 的长度为  2

In [4]: length_message({'a':1,'b':{'c':3,'d':4}})
{'a': 1, 'b': {'c': 3, 'd': 4}} 的长度为  2

多态形式是Python编程方式的核心,有时称为鸭子类型。”如果走起来像鸭子,叫起来像鸭子,那么它就是鸭子。”

7.1.2 封装

定义好类后,对外部隐藏有关工作原理的细节。

class = class()
In :class.add(1,5)
Out:6

In :class.mutiply(2,3)
Out:6

In:class.add([4,5,'a'],{'a':1,'b':2} )
Out:Wrong!不同于多态,多态隐藏的是对象所属的类(对象类型),而封装隐藏的是内部原理和细节。

7.1.3 继承

可基于通用类创建专用类。

class1 = add()
#class1方法 class1.sum()

class2 = add_print(class1)
#调用class1中的方法
class2.sum()

7.2 类

面向对象(OOP)术语 解释
对具有相同数据和方法的一组对象的描述或定义。
对象 对象是一个类的实例。
实例(instance) 一个对象的实例化实现。
标识(identity) 每个对象的实例都需要一个可以唯一标识这个实例的标记。
实例属性(instance attribute) 一个对象就是一组属性的集合。
实例方法(instance method) 所有存取或者更新对象某个实例一条或者多条属性的函数的集合。
类属性(classattribute) 属于一个类中所有对象的属性,不会只在某个实例上发生变化
类方法(classmethod) 那些无须特定的对性实例就能够工作的从属于类的函数。

7.2.1 类是什么

类对象 类方法 类属性
菠萝 泡盐水
凤梨 吃; 引发“凤梨是菠萝吗“的话题。 不泡盐水

举例:菠萝和凤梨,菠萝是凤梨的超类,凤梨是菠萝的子类。
菠萝只有一个方法“吃“,凤梨新增了一个方法:让人们辩论凤梨是不是菠萝?

我们称菠萝为“类对象“,a = 菠萝,b = 菠萝,称 a 和 b 是类对象“菠萝“的两个实例;类对象中的方法称之为“类方法“;泡盐水和不泡盐水又称“类属性“。

7.2.2 创建自定义类

简单示例:

class person:
    def set_name(self,name):
        self.name = name
    def get_name(self):
        return self.name
    def greet(self):
        print('Hello,world! I\'m {}.'.format(self.name))

这个示例包含三个方法定义,它们类似于函数定义,但位于class语句内。
person是类的名称。

foo = person()
foo.set_name("Wei Wu")
foo.greet()

Out:Hello,world! I'm Wei Wu.

其中,self 指向对象 foo 本身。开始我有一种想法,那么 foo.greet()等同于 person.greet(person())吗?实验发现不行,类本身不能作为对象,foo.greet() 可以用下面的形式表达。

person.greet(foo)

Out:Hello,world! I'm Wei Wu.

7.2.3 属性、函数和方法

方法 关联的方法,将参数 self 关联到了该方法所属的实例
函数 无需关联到实例

区别在用代码展示如下:

# 展示一个类中的函数,虽然有self函数,但是其并没有关联到类对象
class Class:
    def method():
        print("This Function is not to associated with the instance it belongs to!")

----------

In : c = Class()
.... c.method()
Out: This Function is not to associated with the instance it belongs to!

7.2.4 再谈隐藏

在默认情况下,允许从外部访问对象的属性。如下:

# 通过示例,
class Class:
    def set_name(self, name):
        self.name = name

    def get_name(self):
        return self.name

----------

In : c = Class()
.... c.set_name('Wei Wu') # 调用方法访问并更改属性
.... c.name
Out: 'Wei Wu'

In : c.name = 'Ling Yu' # 可以从外部直接访问并更改属性
.... c.name
Out: 'Ling Yu'

有些程序员认为,这违反了封装原则。他们认为英爱对外部完全隐藏对象的状态,即不能从外部访问他们。

因此,可以将属性定义为私有。私有属性不能从对象外部访问,而只能通过存取器方法来访问

Python没有为私有属性提供直接的支持,但是可以通过小花招实现类似的效果(仍无法禁止别人访问)。如下示例。

class Secretive: # 遮遮掩掩的
    def __inaccessible(self): # 难以达到的
        print('Bet you can\'t see me ...') # 赌你看不见我

    def accessible(self):
        print('The secret message is :')
        self.__inaccessible()

----------

In : s = Secretive()
.... s.accessible()
Out: The secret message is :
.... Bet you can't see me ...

就同前面所说,这只是一个类似方法,从外部依然可以访问,比如,修改代码,在开头加一个下划线和类名。

class Secretive:
        # 注意这里
    def _Secretive__inaccessible(self): 
        print('    Bet you can\'t see me ...') 

    def accessible(self):
        print('The secret message is :')
        self.__inaccessible()


----------
In : s = Secretive()
.... s._Secretive__inaccessible()
Out:     Bet you can't see me ...

总之,你无法禁止别人访问对象的私有方法和属性,但是这种名称修改方式(对两个下划线打头的名称进行转换,开头加上一个下划线和类名)发出了强烈的信号,让他们不要这样做。比如, from module import * 不会导入以一个下划线打头的名称。

7.2.5 类的命名空间

在 class 语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的
,而类的所有成员都可以访问这个命名空间。类定义其实就是要执行的代码段。比如:

class C:
    print('class C being defined...')


----------
Out: class C being defined ...

在下面的代码中,在类作用域内定义了一个变量,所有的成员(实例)都可以访问它,这里使用它来计算类实例的数量。使用 init 来初始化所有实例。

# 每个实例都可以访问类作用域内的变量

class MemberCounter:
    members = 0
    def init(self):
        MemberCounter.members += 1


----------
In : m1 = MemberCounter()
.... m1.init()
.... m1.members
Out: 1

In : m2 = MemberCounter()
.... m2.init()
.... m2.members
Out: 2

In : MemberCounter.members
Out: 2

如果在一个实例中给属性 members 赋值。

# 局部变量遮盖了全局变量

In : m1.members = 'Two'
In : m1.members
Out: 'Two'

In : m2.members
Out: 1

7.2.6 指定超类

子类拓展了超类的定义。要制定超类,可在 class 语句中的类名后面加上超类名,并将其用圆括号括起。

# 定义了一个过滤序列的通用类 

# 返回不为空的元素
class Filter:
    def init(self):
        self.blocked = []
    def filter(self, sequence ):
        # 返回不在blocked列表中的元素
        return [x for x in sequence if x not in self.blockedd]

#返回不为 ‘SPAM‘的元素
class SPAMFilter(Filter): #是FIlter的子类
    def init(self): # 重写超类 Filter 的方法 init
        self.blocked = ['SPAM'] # 定义列表元素为‘SPAM‘


----------
# 超类从空列表中返回了所有元素
>>> f = Filter
... f.init()
... f.filter([1,2,3])
[1,2,3]

# 子类从列表 blocked 中返回了不为 ‘SPAM‘ 的元素
>>> f = SPAMFilter()
... f.init()
... f.filter([1,2,3,'SPAM','SPAM'])
[1,2,3]

在 SPAMFilter 类的定义中有两个要点:

  • 以提供新定义的方式重写了 FIlter 类中方法 init 的定义;
  • 直接从 Filter 类继承了方法 filter 的定义,因此无需重新编写其定义。

7.2.7 深入探讨继承

要确定一个类是否是另一个类的子类,可使用内置方法 issubclass。

>>> issubclass(SPAMFIlter, Filter)
True

>>> issubclass(Filter, SAMPFilter)
>False

如果你有一个类,想知道它的基类(超类),可以访问其特殊属性 bases (前后双下划线)。

>>> SPAMFilter.__bases__
(<class__main__.Filter at 0x171e40>,)

>>> FIlter.__bases__
(<class 'object'>,)

要确定对象是否是特定类的实例,可以使用 isinstance。

# s 是 SPAMFilter 的实例(直接)
>>> s = SPAMFilter()
>>> isinstance(s, SPAMFilter)
True

# s 也是 Filter 的实例(间接),因为 SPAMFilter 是 FIlter 的子类
>>> isinstance(s, Filter)
True

# s 不是 字符串的实例,即 s 不是字符串类型
>>> isinstance(s, str)
False

如果要获悉对象属于哪个类,可以使用属性 class

>>> s.__class__
<class__main__.SPAMFilter at 0x1707c0>

7.2.8 多个超类

子类可以继承多个超类。

# eval : eval("x") ➡️ x, x可为字符串、字典、元组等.
# 注:若函数内参数有引号,则 x两侧的引号不能与其相同

class Calculator: # 计算器
    def calculate(self, expression):
        self.value = eval(expression)

class Talker:
    def talk(self):
        print('Hi,my value is ',self.value)

# 方法解析顺序(MRPO)
# 先访问 Calculator,再访问 Talker。 顺序反过来的话读取顺序也随之改变。
class TalkingCalulator(Calculator, Talker):
    pass


----------
>>> tc = TalkingCalulator()
>>> tc.calulate('1 + 2 * 3')
>>> tc.talk()
Hi,my value is 7

子类 TalkingCalulator 本身无所作为,其所有行为都是从超类那里继承的。这被称为多重继承。然而,除非万不得已,否则应避免使用多重继承,因为在有些情况下,它可能带来意外的“并发症“。

使用多重继承时,如果 Calculator 类包含方法 talk,那么这个方法将覆盖 Talker 类的方法 talk。因此,当多个超类以不同的方式实现了同一个方法,必须在 class 语句中小心排列这些超类。

Python搜索——广度搜索和深度搜索。

class A: # Python3,不需要加 (object)

    def test(self):

        print('from A')



class B(A):

    def test(self):

        print('from B')



class C(A):

    def test(self):

        print('from C')



class D(B):

    def test(self):

        print('from D')



class E(C):

    def test(self):

        print('from E')



class F(D,E):

    # def test(self):

    #     print('from F')

    pass

f1=F()

f1.test()

print(F.__mro__)

Out:from D
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

#疑问,这里到底是广度搜索还是深度搜索?看着是深度搜索,当构建一颗圆满的二叉树(4层15个节点)时,是按照深度搜索做的。

7.2.9 接口和内省

函数 描述 语法 参数 返回值
hasattr 用于判断对象是否包含对应的属性。 hasattr(object, name) object – 对象 name – 字符串,属性名。 如果对象有该属性返回 True,否则返回 False
getattr 用于返回一个对象属性值 getattr(object, name[, default]) object – 对象。name – 字符串,对象属性。default – 默认返回值,如果不提供该参数,在没有对应属性时,将触发 AttributeError。 返回对象属性值。
callable 用于检查一个对象是否是可调用的。如果返回True,object仍然可能调用失败;但如果返回False,调用对象ojbect绝对不会成功 callable(object) object – 对象 可调用返回 True,否则返回 False

处理多态对象时,只关心其接口(协议)——对外暴露的方法和属性。通常,要求对象遵循特定的接口(即实现特定的方法),在有需要时,检查方法是否存在,如果不存在,就改弦易辙。

# 经检测,在实例 tc 中包含方法 talk
>>> hasattr(tc, 'talk)
True

# 经检测,在实例 tc 中不包含方法 hah
>>> hasattr(tc, 'hah')
False

还可以检查属性 talk 是否可被调用

>>> callable(getattr(tc,'talk',None))
True

>>> callable(gettatts(tc,'hah',None))
False

setattr 与 getattr 功能相反,可用于设置对象的属性

>>> setattr(tc, 'name', 'Wei Wu')
>>> tc.name
'Wei Wu'

7.2.10 抽象基类

什么是抽象类?

抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化

为什么要有抽象类

  • 从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
  • 从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。

个人理解

抽象类,定义了在此类中必须实现方法。如果子类中没有实现,那么就会报错。

# 定义抽象类 All_file

#[注:本代码黏自博客园qianxiamo](https://www.cnblogs.com/asaka/p/6758426.html)

from abc import ABC,abstractmethod #利用abc模块实现抽象类

class All_file(ABC):

    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能

    def read(self):
        '子类必须定义读功能'
        pass  

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能' 
        pass  

# class Txt(All_file):
#     pass 

# t1=Txt() #报错,子类没有定义抽象方法
# 派生子类,实现抽象类方法

#[注:本代码黏自博客园qianxiamo](https://www.cnblogs.com/asaka/p/6758426.html)

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法

    def read(self):
        print('文本数据的读取方法') 

    def write(self):
        print('文本数据的读取方法') 


class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法

    def read(self):
        print('硬盘数据的读取方法') 

    def write(self):
        print('硬盘数据的读取方法')


class Process(All_file): #子类继承抽象类,但是必须定义read和write方法

    def read(self):
        print('进程数据的读取方法')



    def write(self): 
        print('进程数据的读取方法')

----------
>>> wenbenwenjian=Txt() 
>>> yingpanwenjian=Sata()
>>> jinchengwenjian=Process()

>>> wenbenwenjian.read()
文本数据的读取方法

>>> yingpanwenjian.write()
硬盘数据的读取方法

>>> jinchengwenjian.read()
进程数据的读取方法 

>>> print(wenbenwenjian.all_type)
file

>>> print(yingpanwenjian.all_type) 
file

>>> print(jinchengwenjian.all_type)
file

7.3 关于面向对象设计的一些思考

见书


End


猜你喜欢

转载自blog.csdn.net/weixin_37392582/article/details/80342989