一文学习Python面向对象

Python面向对象

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

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

Python对象

self浅析

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

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

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

// 毕达哥斯拉定理计算两点之间的距离import mathclass Point:def move(self, x, y):self.x = xself.y = ydef 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文件识别为两个不同的文件。


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_stringself.__pass_phasse = pass_phrasedef decrypt(self, pass_phrase):''' Only show the string if the pass_phrase is correct.'''if pass_phrase == self.__pass_phasse:return self.__plain_stringelse: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 = nameself.email = emailContact.all_contact.append(self)c = Contact('c', '123@.com')
d = Contact('d', '123@.com')
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 = nameself.email = email// 定义一个继承Contact的子类Friend
class Friend(Contact):// 重写父类 __init__方法def __init__(self, name, email, phone):self.name = nameself.email = emailself.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中有任何的深度(层级)。在多级继承中,基类和派生类的特性被继承到新的派生类中。

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

在这里插入图片描述

  • 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 Falsereturn Truefrom collections import ContainerOdd_Container = OddContainer()
print(isinstance(Odd_Container, Container))
print(issubclass(OddContainer, Container))True
True

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

创建抽象基类

先看一个例子吧

import abc
class MediaLoaser(metaclass=abc.ABCMeta):@abc.abstractclassmethoddef play(self):pass@abc.abstractpropertydef ext(self):pass@classmethoddef __subclasshook_(cls, C):if cls is MediaLoaser:attrs = set(dir(C))if set(cls.__abstractmethods__) <= attrs:return Truereturn NotImplemented

@classmethod .

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

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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部