一文学习Python面向对象

Python面向对象

前言:很多人可能用Python语言很长时间了,但主要是用Python写一些业务,出于多方面的考虑,对面向对象一直是敬而远之。虽然Python因为其特性而显得面向对象在实际业务中不是特别重要,但能写出高质量的代码难道不是很开心吗?那么这篇文章就是单独简要的剖析Python的面向对象,往下看吧,不用多久。

这篇文章的写作思想主要来自于《Python 3 面向对象编程》(第2版)Dusty Phillips著

Python对象

self浅析

首先是self,如果你了解其他语言的面向对象,self与this大概起到了类似的作用。

self参数就是对方法所调用对象的引用。我们可以像对其他对象一样访问这一对象的属性和方法。

在Python的面向对象中,似乎每一类内的函数都需要有self这个参数

// 毕达哥斯拉定理计算两点之间的距离

import math


class Point:
    def move(self, x, y):
        self.x = x
        self.y = y
 
    def reset(self):
        self.move(0.0)

    def calculate_distance(self, other_point):
        return math.sqrt(
        (self.x - other_point.x) ** 2 + 
        (self.y - other_point.y) **2 )

ok,Python里的面向对象同时也有字符串文档(docstring),来支持文档注释。可以在每个类、函数或方法头的定义语句之后添加。可以用三个单引号或三个双引号包裹。

模块和包

import大法好呀!

命名空间(namespace),模块或函数中当前可访问的名称列表,简单的说就是通过引入命名空间,可以在一个Python项目中将A文件夹里的HelloWorld.py文件和B文件夹里的HelloWorld.py文件识别为两个不同的文件。

一文学习Python面向对象_第1张图片
from database import *

如果理解了命名空间的概念,想必看到上面的一行代码会产生一些不适感,因为会污染命名空间啊!

  1. 与 from database import Database相比,上述代码可能会让我们在database源文件里大量的代码中花费大量的时间找到Database的位置;

  2. 编辑器一般会提供额外的功能,例如代码补全,跳转到类定义的位置或者行内注释等,import *很可能会破坏他们;

  3. import * 不仅会导入模块中所有已定义的类和函数,同时也会导入这个模块本身所导入的类和模块。

此外,还有一些绝对导入和相对导入的内容。

权限限制

Python中访问权限包括:公有的,私有的,受保护的。

good!上面说的是完全错误的。事实上,Python不相信那些将来会对你造成妨碍的强制性规矩,相反提供的是非强制性的指南和最佳实践。严格的说,类的所有方法和属性都是对外公开的。如果想说明某个方法不应该公开使用,可以在它的文档字符串中表明这个方法仅限于内部使用。(更好的做法是,介绍对开公开的API如何使用)

在属性或方法前加一个下划线字符_的前缀。表明“这是一个内部变量,直接访问它之前请务必三思”,如果程序员们坚持认为需要进行访问,则编译器是阻止不了他们的行为。

难道只能这样不能拒绝公开访问吗?一种更加强势的表明外部对象不能访问某个属性或方法:用双下划线__作为前缀。这会对前缀修饰的属性或方法施加命名改装(name mangling)。例如:

class SecretString:
    '''A not-at-all secure way to store a secret string '''
    def __init__(self, plain_string, pass_phrase):
        self.__plain_string = plain_string
        self.__pass_phasse = pass_phrase
        
    def decrypt(self, pass_phrase):
        ''' Only show the string if the pass_phrase is correct.'''
        if pass_phrase == self.__pass_phasse:
            return self.__plain_string
        else:
            return "
>>> sample = SecretString("HelloWorld", "123")
>>> print(sample.decrypt("123"))
HelloWorld
>>> print(sample.__plain_string)
Traceback (most recent call last):
  File "", line 1, in <module>
AttributeError: 'SecretString' object has no attribute '__plain_string'

看起来是有效的,在密码不正确或者不输入密码时,不能访问plain_string属性,所以踏实安全的,但是不要高兴太早,来看看所谓的安全措施是多么容易被破解。

>>> print(sample._SecretString__plain_string)
HelloWorld

好吧,名字改装只是将双下划线的属性会加上_<类名>的前缀,当方法在类内部访问时会被自动的改回去。

所以说,Python是一个开放的语言,名字改装也不能保证私有性,它只是强烈建议保持私有。除非有非常非常强有力的理由,Python程序员很少在别的对象用到双下划线的变量。

对象相似(Samlpe Object)

继承

OK,所有创建的类都使用了继承关系,希望你能接受。所有的Python类都是特殊的object子类。这个类提供很少量的数据和行为(它提供的所有方法都是双下划线开头的特殊方法)

class Contact():
    all_contact = []
    def __init__(self, name, email):
        self.name = name
        self.email = email
        Contact.all_contact.append(self)

c = Contact('c', '[email protected]')
d = Contact('d', '[email protected]')
for i in range(len(Contact.all_contact)):
    print(Contact.all_contact[i].name)

--------------------------------------------------------------------------
c
d

扩展内置对象

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


class Dog(Animal):

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

    // 扩展方法
..  def eat(self):
        print('Eating meat...')ing...')

语法糖

重写与super

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


class Dog(Animal):
    
    // 重写父类run()方法
    def run(self):
        print('Dog is running...')

class Cat(Animal):

    // 重写父类run()方法
    def run(self):
        print('Cat is running...')
class Contact():
    def __init__(self, name, email):
        self.name = name
        self.email = email

// 定义一个继承Contact的子类Friend
class Friend(Contact):
    // 重写父类 __init__方法
    def __init__(self, name, email, phone):
        self.name = name
        self.email = email
        self.phone = phone

// super可以返回父类实例化得到的对象
class Friend(Contact):
    // 使用super重写父类__init__函数
    def __init__(self, name, email, phone):
        super().__init__(name, email)
        self.phone = phone

这个例子中首先用super获取父类对象的实例,然后调用它的__init__方法,传入预期的参数。然后执行它自己的初始化过程,也就是设定phone属性。

任何方法和任意位置都可以调用super

多重继承

此部分参考内容:

Python多重继承之菱形继承 - 西加加先生 - 博客园

Python super() 详解 最简单的解释_芝士锅的博客-CSDN博客_python super

继承是面向对象编程的一个重要的方式,通过继承,子类就可以扩展父类的功能。在python中一个类能继承自不止一个父类,这叫做python的多重继承

另一方面,我们也可以继承一个派生类的形式。这被称为多级继承。 它可以在Python中有任何的深度(层级)。在多级继承中,基类和派生类的特性被继承到新的派生类中。

在多层继承和多继承同时使用的情况下,就会出现复杂的继承关系,多重多继承。其中,就会出现菱形继承。

一文学习Python面向对象_第2张图片

  • D->B->A->C(深度优先)
  • D->B->C->A(广度优先)
class A():
    def __init__(self):
        print('init A...')
        print('end A...')

class B(A):
    def __init__(self):
        print('init B...')
        A.__init__(self)
        print('end B...')

class C(A):
    def __init__(self):
        print('init C...')
        A.__init__(self)
        print('end C...')

class D(B, C):
    def __init__(self):
        print('init D...')
        B.__init__(self)
        C.__init__(self)
        print('end D...')

if __name__ == '__main__':
    D()

结果:

init D...
init B...
init A...
end A...
end B...
init C...
init A...
end A...
end C...
end D...

从输出结果中看,调用顺序为:D->B->A->C->A。可以看到,B、C共同继承于A,A被调用了两次。A没必要重复调用两次。

其实,上面问题的根源都跟MRO有关,MRO(Method Resolution Order)也叫方法解析顺序,主要用于在多重继承时判断调的属性来自于哪个类,其使用了一种叫做C3的算法,其基本思想时在避免同一类被调用多次的前提下,使用广度优先和从左到右的原则去寻找需要的属性和方法。

那么如何避免顶层父类中的某个方法被多次调用呢,此时就需要super()来发挥作用了,super本质上是一个类,内部记录着MRO信息,由于C3算法确保同一个类只会被搜寻一次,这样就避免了顶层父类中的方法被多次执行了,上面代码可以改为:

class A():
    def __init__(self):
        print('init A...')
        print('end A...')

class B(A):
    def __init__(self):
        print('init B...')
        super(B, self).__init__()
        print('end B...')

class C(A):
    def __init__(self):
        print('init C...')
        super(C, self).__init__()
        print('end C...')

class D(B, C):
    def __init__(self):
        print('init D...')
        super(D, self).__init__()
        print('end D...')

if __name__ == '__main__':
    D()  

结果:

init D...
init B...
init C...
init A...
end A...
end C...
end B...
end D...

可以看出,此时的调用顺序是D->B->C->A。即采用是广度优先的遍历方式。

Python类分为两种,一种叫经典类,一种叫新式类。都支持多继承,但继承顺序不同。

  • 新式类:从object继承来的类。(如:class A(object)),采用广度优先搜索的方式继承(即先水平搜索,再向上搜索)。
  • 经典类:不从object继承来的类。(如:class A()),采用深度优先搜索的方式继承(即先深入继承树的左侧,再返回,再找右侧)。

Python2.x中类的是有经典类和新式类两种。Python3.x中都是新式类。

补充:新式类真的是广度优先吗?

嘿嘿

super()函数

super()和父类没有实质性的关联。如果想要搞懂super() 函数的运行原理,那一定要先搞懂 mro 属性, mro 是Method Resolution Order,中文方法解析顺序。单继承中super() 函数使用比较简单的原因 也是因为 mro 比较简单,多继承的__mro__就稍微复杂了,总之 __mro__的目的就是 按照一定顺序,保证父类的函数只调用一次。

经典类的MRO方法是采用 从左至右的深度优先遍历 算法,重复留前者
新式类的MRO方法是采用 从左至右的深度优先遍历 的算法,重复留后者

详情参见第二个链接
不同集合的参数

关键字参数

可以扩展函数的功能。关键字参数允许你传入0个或任意个含参数名的参数,0意味着关键字参数可填可不填,这些关键字参数在函数内部自动组装为一个dict。

多重继承看起来似乎不错,但有很多容易引发错误的地方,一种更加清晰的方式将类组合在一起,详见设计模式

多态

由于所有子类不同而产生的不同行为,而不需要明确知道用的是哪个子类。

“开闭”原则

对扩展开放:允许新增子类;

对修改封闭:不需要修改依赖超类类型的函数。

鸭子类型

动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。 ——廖雪峰

鸭子类型不需要提供所需对象的整个接口,而只需要满足实际被访问的接口。

抽象基类

ABCs抽象基类(Abstract base class)

  • 抽象基类的特点

    • 不能被实例化

      抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体的接口。

    • 子类必须实现抽象基类的方法
      抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。

class OddContainer:
    def __contains__(self, x):
        if not isinstance(x, int) or not x % 2:
            return False
        return True

from collections import Container

Odd_Container = OddContainer()
print(isinstance(Odd_Container, Container))
print(issubclass(OddContainer, Container))


True
True

可以判断出即使没有继承Container,这个类仍然是一个Container对象。

创建抽象基类

先看一个例子吧

import abc
class MediaLoaser(metaclass=abc.ABCMeta):
    @abc.abstractclassmethod
    def play(self):
        pass
    
    @abc.abstractproperty
    def ext(self):
        pass
    
    @classmethod
    def __subclasshook_(cls, C):
        if cls is MediaLoaser:
            attrs = set(dir(C))
            if set(cls.__abstractmethods__) <= attrs:
                return True
            
        return NotImplemented

@classmethod .

这个装饰器将方法标记为类方法,也就是说这个方法可以通过类而不是实例对象调

Good,Python面向对象的概念到此就讲解完毕了!撒花✿✿ヽ(°▽°)ノ✿

你可能感兴趣的:(Python,Web3,python,学习,开发语言)