前言:很多人可能用Python语言很长时间了,但主要是用Python写一些业务,出于多方面的考虑,对面向对象一直是敬而远之。虽然Python因为其特性而显得面向对象在实际业务中不是特别重要,但能写出高质量的代码难道不是很开心吗?那么这篇文章就是单独简要的剖析Python的面向对象,往下看吧,不用多久。
这篇文章的写作思想主要来自于《Python 3 面向对象编程》(第2版)Dusty Phillips著
首先是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文件识别为两个不同的文件。
如果理解了命名空间的概念,想必看到上面的一行代码会产生一些不适感,因为会污染命名空间啊!
与 from database import Database相比,上述代码可能会让我们在database源文件里大量的代码中花费大量的时间找到Database的位置;
编辑器一般会提供额外的功能,例如代码补全,跳转到类定义的位置或者行内注释等,import *很可能会破坏他们;
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程序员很少在别的对象用到双下划线的变量。
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...')
语法糖
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中有任何的深度(层级)。在多级继承中,基类和派生类的特性被继承到新的派生类中。
在多层继承和多继承同时使用的情况下,就会出现复杂的继承关系,多重多继承。其中,就会出现菱形继承。
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类分为两种,一种叫经典类,一种叫新式类。都支持多继承,但继承顺序不同。
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面向对象的概念到此就讲解完毕了!撒花✿✿ヽ(°▽°)ノ✿