该文章内容整理自《Python编程:从入门到实践》、《流畅的Python》、以及网上各大博客
文章目录
类
Python是面向对象编程的语言,封装、继承、和多态是其三大特征。对于封装性,如将多种不同数据放到列表中就是一种数据层面的封装;把常用的代码块打包成一个函数也是一种语句层面的封装。而类则是一种更好的封装
类提供了一种组合数据和功能的方法。 创建一个新类意味着创建一个新的对象 类型,从而允许创建一个该类型的新实例 。和其他编程语言相比,Python 用非常少的新语法和语义将类加入到语言中。它是 C++ 和 Modula-3 中类机制的结合。Python 的类提供了面向对象编程的所有标准特性:类继承机制允许多个基类,派生类可以覆盖它基类的任何方法,一个方法可以调用基类中相同名称的的方法
一个类由三大组成部分,分别是
- 类名class_name
- 继承关系class_bases
- 类的名称空间class_dict
类的定义和实例化一般形式如下。其中__init__()为类的构造函数。self为类实例对象本身。另外,self只是约定俗成的名称,并不是Python中的关键词,因而可以将其声明为其他名称。
class ClassName:
def __init__(self, a):
self.a = a
#def __init__(test, a):
# test.a = a
obj = ClassName()
类成员
实例属性、类属性和局部变量
类是独立的命名空间。在类定义中,根据变量定义位置及方式的不同,类中变量分为以下 3 种类型:
- 实例属性:在类体中,所以函数内部,以“self.变量名”的方式定义的变量。实例属性只能通过实例化对象名访问,无法通过类名访问。当通过实例化对象名为类动态添加一个新属性时,注意只是在这个实例化对象中拥有新属性,在这个类的其他实例化对象中并不存在该新属性
class Test :
def __init__(self, a):
self.a = a
t = Test(1)
t.b = 0 # 动态添加属性
- 类属性:在类体中、所有函数之外定义的变量。这些属性属于类,即所有类的实例化对象都共享类属性。类属性的调用、添加、和修改可通过类名进行访问。也可以通过实例化对象进行调用,但是当通过实例化对象进行添加或修改时,本质将不再是修改类变量的值,而是在给该对象定义新的实例属性。同时,在类中,类属性和实例属性可以同名,但这种情况下使用类对象将无法调用类属性,而会首选实例属性,因此不推荐通过实例化对象名调用类属性。注意类对象存在于类的__dict__属性中,而不存在与实例化对象的__dict__属性中
class Test :
a = "A"
def info(self):
pass
- 局部变量:在类体中,所有函数内部,以“变量名=变量值”的方式定义的变量。当函数执行结束时局部变量也会被销毁
实例方法、类方法和静态方法
在Python中,在类内定义的实例方法和类方法称为方法(method);而在类内定义的静态方法和类外定义的函数称为函数(function)。即与类和实例有绑定关系的function都属于方法;与类和实例无绑定关系的function都属于函数
和类属性的分类不同,类方法是通过函数修饰器进行分类的
- 实例方法:类中定义的不用任何修改的方法为实例方法。实例方法必须包含 self 参数,用于绑定调用此方法的实例对象。此时用type()查看函数类型为method,即绑定方法,该方法绑定实例对象,因而需要传入 self
class Test:
def func(self):
pass
t = Test()
t.func() # 通过实例化对象调用实例方法
Test.func(t) # 通过类名调用实例方法,此时需要手动给self传入实例化对象
print(type(t.func)) # <class 'method'>
# 通过类名动态添加实例方法,影响类的全部实例
def info1(self):
pass
Test.info1 = info1
# 通过实例化对象名动态添加实例方法,只影响实例化对象
def info2(): # 若这里添加self参数,在调用时需要手动传入参数
pass
t.info2 = info2
- 类方法:类中定义的采用 @classmethod 修饰的方法为类方法。类方法必须包含 cls 参数,用于绑定调用此方法的类。和 self 一样,cls 参数的命名也不是规定的,只是约定俗称的习惯。类方法推荐使用类名直接调用,当然也可以使用实例对象来调用,但并不推荐使用这种方法。此时用type()查看函数类型为method,即绑定方法,该方法绑定类对象,因而需要传入 cls
class Test:
@classmethod
def func(cls):
print("正在调用类方法", cls)
# 动态添加类方法
@classmethod
def info(cls):
pass
Test.info = info
- 静态方法:类中定义的采用 @staticmethod 修饰的方法为静态方法。静态方法定义在类命名空间中,而函数则定义在全局命名空间中。静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。可通过类名或实例化对象来调用静态方法。实际上,对静态方法而言,类只相当于一个命名空间,完全可以在类外面写一个同样的函数,但是这样做就打乱了逻辑关系,也会导致以后代码维护困难。此时用type()查看函数类型为function,即非绑定方法,该方法不绑定对象,因而不用传入 self 或 cls
class Test:
@staticmethod
def func(a, b):
print(a, b)
# 动态添加静态方法
@staticmethod
def info():
pass
Test.info = info
公有成员、保护成员和私有成员
Python不同于C++会在公有和私有成员之间进行很强的区分,实际上,Python中并不提供严格意义上的公有成员和私有成员。为了实现类的封装性,Python采用下划线命名来进行区分
- 不以下划线开头:表示公有成员
- 以单下划线开头:表示为私有成员。类中的以单下划线开头的属性和函数仅是一个提醒,表明该成员仅供内部使用,实际上外部仍能引用。但在模块中定义了以单下划线开头的变量和函数,并使用import引入该模块时,外部并不能引用这个变量和函数
- 以单下划线结尾:为了区别该名称与关键词
- 以双下划线开头:表示为私有成员,外部不能直接通过成员名引用。Python解释器会重写该成员名称,将其改为_class__member,因而外部想要访问以双下划线开头的“私有成员”时,引用的名称应该为 _class__member
- 以双下划线开头,双下划线结尾:表示Python内部的名字,此时解释器并不会重写该成员名称。Python不建议将自己命名的方法写为这种形式
- 单下划线变量:表示临时变量。在Python交互式窗口中则可以表示为最近一个表达式的结果。如
for _ in range(10): print("Hello")
car = ('red', 'auto', 12, 3812.4)
color, _, _, mileage = car
双下划线成员
Python 提供了dir() 函数,当函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。如
print(dir()) # 获得当前模块的属性列表
# ['__builtins__', '__doc__', ...]
print(dir([ ])) # 查看列表的方法
# ['__add__', '__class__', ...]
class a(object):
pass
print(dir(a())) # 查看一个最简单的类的所有属性和方法
# ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
常见双下划线属性
- __class__:表示类的类型,type(obj.__class__)返回就是type类型。obj.__class__返回的是实例化对象的类的名称
- __module__:返回实例化对象所属模块
- __dict__:用来存储类所有可写属性的字典
- __doc__:返回类的注释信息。该属性无法被继承。可通过类名调用
- __slots__:用来限制为实例对象动态添加属性和方法,但无法限制动态地为类添加属性和方法。__slots__属性值其实是一个元组,只有其中指定的元素才可以作为动态添加的属性或者方法的名称,如将类属性__slots__ = (‘name’, ‘add’, ‘info’),此时该类的实例对象仅限于动态添加 name、add、info 这 3 个属性以及 name()、add() 和 info() 这 3 个方法。注意,对于动态添加的方法,__slots__限制的是其方法名,并不限制参数的个数。此外,__slots__属性只对当前所在的类起限制作用,对派生的子类不起作用。如果为子类也设置有 __slots__属性,那么子类实例对象允许动态添加的属性和方法,是子类中__slots__属性和父类 __slots__属性的和
常见双下划线方法
- __new__():创建一个对象,实例化时会调用__new__()方法。__new__()方法至少要有一个参数cls,其代表要实例化的类,此参数在实例化时由解释器自动提供。方法返回的是实例化对象,然后再由这个实例化对象调用__init__()方法来初始化,并且该实例化对象就是__init__()方法中的self。也就是说,__new__()创造出了对象,但是这个对象是空的,名称空间的装备需要通过__init__()来完成
class NewTest():
def __new__(cls, *args, **kwargs):
print("__new__")
return object.__new__(cls)
def __init__(self, a, b):
print("__init__")
self.a = a
self.b = b
# 单例模式是__new__()的一个应用,即一个类始终是有一个实例
class Foo:
__instance = False
def __new__(cls, *args, **kwargs):
if cls.__instance: return cls.__instance
cls.__instance = object.__new__(cls)
return cls.__instance
- __init__():为构造函数,在初始化实例时调用
- __call__():当对象加 () 时会调用__call__()方法。能实现类似C++中的仿函数的功能。如下面程序。另外,可用callable(object) 函数来检查一个对象object是否是可调用的。如果返回 True,object 仍然可能调用失败;但如果返回 False,调用对象 object 绝对不会成功。 对于函数、方法、lambda 函式、 类以及实现了 __call__()方法的类实例, 它都返回 True。另外,注意__call__()是实例方法,只能通过实例化对象名加()调用,而不能通过类名加()调用
class CallTest():
def __init__(self):
print("__init__")
def __call__(self):
print("__call__")
obj = CallTest() # 触发构造函数
obj() # 触发__call__
- __getitem__():在中括号取值时触发。如 obj[“a”] 时
- __setitem__():在中括号赋值时触发。如 obj[“a”] = 1 时,但不会触发__getitem__()。注意此时是给实例化对象添加一个实例属性,其他实例不受影响
- __delitem__():在中括号删除时触发。如 del obj[“a”] 时,但不会触发__getitem__()
class itemTest:
def __getitem__(self, item):
print('getitem执行', self.__dict__[item])
def __setitem__(self, key, value):
print('setitem执行')
self.__dict__[key] = value
def __delitem__(self, key):
print('delitem执行')
self.__dict__.pop(key)
i = itemTest()
print(i["a"] + 1)
i["a"] = 1
del i["a"]
- __getattr__():调用对象的一个不存在实例属性时会触发__getattr__()方法。此时函数返回值为调用该不存在实例属性的值,且无论在何处调用不存在实例属性时都会调用该函数。下面三个函数同理。但实际上并不推荐人工修改这些方法
- __getattribute__():与__getattr__()方法相似,只是不管实例属性是否存在都会执行。并且,当__getattr__()与__getattribute__()同时存在时只会执行__getattrbute__(),除非__getattribute__()在执行过程中抛出异常
- __setattr__():增加或修改对象实例属性时会触发__setattr__()方法。另外,Python中并不提供和C语言一样的const常量,而一般约定全大写变量为const常量。若要在Python中使用const常量禁止用户修改,可以新建一个类并修改其中的__setattr__()方法,若修改的属性已存在,即不为新增属性,则抛出异常,提示错误
- __delattr__():删除对象的一个实例属性时会触发__delattr__()方法
class AttrTest:
def __getattr__(self, item):
print("__getattr__", str