Python中的类和其他部分一样,也是动态的,类被动态创建,创建后还可以修改。
Python中变量的赋值和传递,操作的总是对象的引用。一个对象可以绑定多个名字,多个变量可以指向同一个对象。
Python的对象更加强调鸭子类型,特定情况下看起来像鸭子,就是鸭子。
命名空间和作用域
命名空间是名字到对象的映射。目前大部分命名空间在python中都是以字典的形式存在的。
命名空间在不同的时刻被创建,同时拥有不同的生命周期。built-in命名空间在python解释器启动时被创建,并且不会被删除;一个模块的全局命名空间在模块定义被读取时创建,一般也会一直存在,直到解释器退出。局部命名空间在函数被调用时创建,在离开函数时被删除。
作用域是程序中命名空间直接对应的文本区域,在该区域,可以不加前缀修饰的直接访问对应的命名空间。作用域是静态决定的,但是被动态的使用。任何时候,至少有3个嵌套的作用域。
- 最内部的作用域,首先被访问,包含局部变量
- 外围函数作用域,按从内到外的顺序,从最里层作用域开始被查找,包含非局部,但是也非全局的变量名
- 全局作用域,次外层作用域,包含模块的全局命名
- 最外层作用域,包含内建的名称
类定义在本地作用域中引入了一个新的命名空间。
global和nolocal
global语句用于指明变量存在于全局作用域,nolocal指明变量存在于外围作用域,除去global和nolocal语句,所有的引入新命名的操作都使用局部作用域。
类对象
类定义语句执行完成后,一个类对象被创建。类对象支持两类操作,属性引用和实例化
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
所有在类的命名空间中的名字都是有效的属性名。 MyClass.i ,MyClass.f和MyClass.__doc__都是有效的属性。
类属性分为数据属性和行为属性,数据属性是类的状态,行为是绑定到类的函数,对应着实例对象的方法。
x = MyClass()
实例化一个MyClass类型的对象,赋值到局部变量x
通过__init__初始化实例对象
def __init__(self):
self.data = []
__init__可以带有其他参数,传入实例化操作符的参数将会被被传递到__init__中。
>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart.
.. self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
实例对象
实例对象仅仅支持属性引用操作。实例属性分为数据属性和方法属性,数据属性是实例的状态,方法是绑定到实例的函数。
x = MyClass()
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
x.counter是实例的数据属性,在赋值时创建,通过del
可删除。
x.f是实例对象的方法,源于MyClass中定义的函数f。MyClass.f是函数,x.f是方法。
>>> x.i
12345
>>> x.i = 54321
>>> x.i
54321
x.i也是数据属性,在不被赋值的情况下引用到类的属性MyClass.i,但是一旦赋值,则引用到属于实例自己的实例属性。
方法对象
方法是对象,所以支持赋值到变量
xf = x.f
while True:
print(xf())
xf() 等同于x.f()
类定义中方法定义的第一个参数(惯例命名为self)指示绑定的实例对象,在方法调用时,该参数被自动被传递。
x.f()等价于MyClass.f(x)
当实例对象的非数据属性被引用,对应的类对象被查找,如果该属性名是是类的一个属性,并且是函数对象,一个方法对象被创建,该对象封装实例对象和对应的函数,当方法对象被传入参数调用,通过实例对象和老的参数列表,一个新的参数列表被生成,随后对应的函数被通过传入该新创建的参数列表调用。
一个函数如果是类的一个属性,则该函数就定义了一个类实例对象的方法,函数本身的定义不需要位于类的定义中。
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
x.i在不被赋值的情况下引用到类的属性MyClass.i,但是一旦赋值,则引用到属于实例自己的实例属性。
在方法中对当前对象其他属性的引用,必须通过方法的第一个参数self引用,并无其他快捷方式。
和函数一样,在方法中可以直接引用全局变量,全局变量所在的作用域是方法所在的模块,通常包含方法的类被直接定义在全局作用域中,类本身不作为全局作用域。虽然很少在方法中引用全局变量,但是有时却是很有用的,比如应用全局作用域中导入的函数,模块,以及模块中的其他元素。
方法对象的属性__self__指向其绑定的实例对象, __func__执行其对应的函数。
类变量和实例变量
类变量是被所有类的实例对象共享的变量,实例变量是唯一属于当个实例对象的变量
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs'canine'
>>> e.kind # shared by all dogs'canine'
>>> d.name # unique to d'Fido'
>>> e.name # unique to e'Buddy'
属性的可见性
Python不提供实例对象属性的可见性配置功能,所有的属性在python中都是公有的,使用属性时如果任务修改了被方法操作,维护的属性,则可能影响整个对象的状态,这会导致很难排查的bug,所以python中尽可能对属性的命名保持一个惯例,尽可能减少属性名的冲突。
对于私有变量,python的惯例是以一个下划线开头的变量被认为是私有变量,对象的用户不应该使用这类变量。
对于类对象的私有变量,为了避免实现子类时的命名冲突,python提供了名字重编的功能,以至少两个下划线开头,至多一个下划线结尾的类变量,python会把用到该变量的地方都改为_ClassName+变量名。
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
Mapping._Mapping__update和Mapping.update都是有效的属性, Mapping.__update却是无效的属性。
继承
继承语法:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
基类的位置可以是任意的表达式,只要求值为一个类对象。
由于属性解析的顺序是子类-》父类,子类可以重载父类的方法,很容易实现多态。
如果在子类重写的方法中需要调用基类的方法,可以使用
BaseClassName.methodname(self, arguments)
或者
super(DerivedClassName, self).methodname(arguments)
isinstance()和issubclass()
isinstance() 用于检查实例的类型。
isinstance(obj, int)当obj.class 是int或int的子类时为真。
issubclass() 用于检查类的继承:。
issubclass(bool, int) bool是int的子类,所以返回真;issubclass(float, int) float不是int的子类所以返回假。
多重继承
多重继承语法:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
简单说来,一个位于父类中的属性的搜索顺序是深度优先,从左到右,在继承树中重复的同一个类不会被搜索两次。实际上方法的解析顺序要复杂一些,是动态变化的。