Python
Python
目录
面向对象编程(封装)
简概
简概
Python 的类机制通过最小的新语法和语义在语言中实现了类。它是C++
或者Modula-3语言
中类机制的混合
就像模块一样,Python 的类并没有在用户和定义之间设立绝对的屏障(指的是C++设了),而是依赖于用户不去“强行闯入定义”的优雅
类的大多数重要特性都被完整的保留下来:
- 类继承机制允许多重继承(C++也是,Java好像比较严谨,不让)
- 派生类可以覆盖(override)基类中的任何方法或类
- 可以使用相同的方法名称调用基类的方法
- 对象可以包含任意数量的私有数据
作用域与命名空间(不是很懂)
命名空间
命名空间 概念
命名空间
是从命名到对象的映射
命名空间 术语
一般称 Python 中任何一个“.”之后的命名为属性
命名空间实现方法
- 当前命名空间主要是通过 Python 字典实现的
- 不过通常不关心具体的实现方式(除非出于性能考虑),以后也有可能会改变其实现方式
命名空间 的一些例子
- 内置命名(像 abs() 这样的函数,以及内置异常名)集
- 模块中的全局命名
- 函数调用中的局部命名
- 某种意义上讲对象的属性集也是一个命名空间
命名空间 作用
- 关于命名空间需要了解的一件很重要的事就是不同命名空间中的命名没有任何联系,例如两个不同的模块可能都会定义一个名为
maximize
的函数而不会发生混淆-用户必须以模块名为前缀来引用它们。
- 关于命名空间需要了解的一件很重要的事就是不同命名空间中的命名没有任何联系,例如两个不同的模块可能都会定义一个名为
命名空间 与生命周期
不同的命名空间在不同的时刻创建,有不同的生存期
例如:包含内置命名的命名空间在Python解释器启动时创建,会一直保留,不被删除
例如:模块的全局命名空间在模块定义被读入时创建,通常,模块命名空间也会一直保存到解释器退出
(内置命名也同样被包含在一个模块中,它被称作 builtins )
例如:当调用函数时,就会为它创建一个局部命名空间,并且在函数返回或抛出一个并没有在函数内部处理的异常时被删除(遗忘)
(其中,每个递归调用都有自己的局部命名空间)
作用域
- 至少有三个命名空间可以直接访问的作用域嵌套在一起
- 三个命名空间
- 首先搜索包含局部命名的使用域,它包含局部命名任意函数包含的作用域,包含非局部、但是也非全局的命名
- 其次搜索的是中层的作用域,这里包含了同级的函数,包含当前模块的全局命名
- 最后搜索最外面的作用域,它包含内置命名,包含内置命名的命名空间
- 三个命名空间
作用域和命名空间demo
def scope_test():
def do_local(): # 仅改变作用域
spam = "local spam"
def do_nonlocal():
nonlocal spam # 能改变上一层的作用域
spam = "nonlocal spam"
def do_global():
global spam # 能改变全局(模块级)的作用域(但不能改变上一层的作用域)
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
''' 输出
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
'''
使用
使用
使用
class Dog(): # 定义(括号在不需要继承时可省略) def __init__(self, name, age): self.m_name = name my_dog = Dog('willie', 6) # 使用
类对象
- 支持两种操作:属性引用和实例化
- 属性引用:
类名.属性
,其中属性可以是数据或方法属性,也可以是__doc__
、__annotations__
等隐藏属性 - 实例化:
对象实例名 = 类名(参数)
- 属性引用:
- 支持两种操作:属性引用和实例化
调试相关
- 每个值都是一个对象,因此每个值都有一个 类( class ) (也称为它的 类型( type ) ),它存储为
object.__class__
- 每个值都是一个对象,因此每个值都有一个 类( class ) (也称为它的 类型( type ) ),它存储为
注意项
数据属性会覆盖同名的方法属性
为了避免意外的名称冲突,这在大型程序中是极难发现的 Bug,使用一些约定来减少冲突的机会是明智的
可能的约定包括:大写方法名称的首字母,使用一个唯一的小字符串(也许只是一个下划线)作为数据属性名称的前缀,或者方法使用动词而数据属性使用名词
类内函数
- 构造函数:使用
__init__()
方法进行实例化 - 成员函数:方法的第一个参数被命名为
self
,包括实例化函数(看起来与C++不同)。但这仅仅是一个约定self
绝对没有任何特殊含义
共享
类变量与实例变量
一般来说,实例变量用于对每一个实例都是唯一的数据,类变量用于类的所有实例共享的属性和方法(仅保留一份副本)
共享方法
在
class
中,构造函数前进行定义(在C++这样做等价于列表初始化语法)且当定义的类型为
可变对象
时,所有类实例共享这一个可变对象
不共享方法
- 如想避免该共享行为,需要在构造函数里去初始化
可变对象
- 如想避免该共享行为,需要在构造函数里去初始化
公有 or 私有(不是很懂)
私有变量
私有变量(前面一个下划线)
描述:只能从对像内部访问的“私有”实例变量,在 Python 中不存在(?)然而,也有一个变通的访问用于大多数 Python 代码
需要注意的是编码规则设计为尽可能的避免冲突,被认作为私有的变量仍然有可能被访问或修改。在特定的场合它也是有用的,比如调试的时候
举例:以一个下划线开头的命名(例如
_spam
)会被处理为 API 的非公开部分(无论它是一个函数、方法或数据成员)
命名编码(前面至少两个下划线,后面至多一个)
- 描述:私有变量不会命名冲突,Python 提供了对这种结构的有限支持,称为
命名编码
(name mangling) - 举例:任何形如
__spam
的标识,被替代为_classname__spam
- 描述:私有变量不会命名冲突,Python 提供了对这种结构的有限支持,称为
类的示例
模拟记录或结构
有时类似于 Pascal 中“记录(record)”或 C 中“结构(struct)”的数据类型很有用,它将一组已命名的数据项绑定在一起
一个空的类定义可以很好的实现它:(其他编译型语言的类很难做到这一点)
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
异常
用户自定义异常也可以是类。利用这个机制可以创建可扩展的异常体系
一般异常类继承Exception
类
继承
使用
使用
class Car(): def __init__(self, make, model, year): pass class ElectricCar(Car): def __init__(self, make, model, year): super().__init__(make, model, year) # 显示调用父类的构造方法但不传self,super用于将父类和子类关联起来
Python2.7的使用(
super()
需要两个实参)class Car(): def __init__(self, make, model, year): pass class ElectricCar(Car): def __init__(self, make, model, year): super(ElectriCar, self).__init__(make, model, year)
概念
- C++里叫
基类
和派生类
,更通俗的叫法为父类
/超类
(superclass)和子类
,名称super
因此得名
- C++里叫
底层原理
- 对于 C++ 程序员来说,Python 中的所有方法本质上都是
虚方法
(C++程序猿狂喜)
- 对于 C++ 程序员来说,Python 中的所有方法本质上都是
Python 有两个用于继承的函数:
函数 isinstance() 用于检查实例类型
函数 issubclass() 用于检查类继承
多继承
使用
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
底层原理与解析顺序(与C++原理不同,C++是编译性的,不会动态查找地址)
解析顺序
父类继承的深度优先、左到右搜索,而不是搜索两次在同一个类层次结构中
例如
如果在
DerivedClassName
中没有找到某个属性,就会搜索Base1
,然后(递归的)搜索其基类如果最终没有找到,就搜索
Base2
,以此类推
改变解析顺序
- super() 可以动态的改变解析顺序。这个方式可见于其它的一些多继承语言,类似 call-next-method,比单继承语言中的 super 更强大
- 动态调整顺序十分必要的,因为所有的多继承会有一到多个菱形关系。为了防止重复访问基类,通过动态的线性化算法,每个类都按从左到右的顺序特别指定了顺序,每个祖先类只调用一次