面向对象概述

面向对象概述

面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。它把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。

例如生活中我们会遇到各种数字及其运算,于是计算机科学家们就把数字抽象出来,成为一类对象。它们有别于其他事物(例如文字,图像)的特征,有自己的一套运算、操作方法。

Python 中的数字当然非常类似数学中的数字,但也受限于计算机中的数字表示方法。

为了方便,又把数字这一类对象,基本分成了整数,浮点数,复数三类对象。这对应 Python 中的 int,float,comlex 三种数字类型,三种数字类型下具体的整数,浮点数,复数也是对象。它们有共同的特征和计算方法,也有各自特有的特征和方法。例如都可以进行加减乘除,只有整数有按位运算。

整数中,0 和 1 的特殊性,又使得可以将整数类中再分出一类对象,布尔类型对象。布尔类型的对象,只有两个对象,True 和 False。

从数字类对象,到三类数字对象,到布尔类对象,到具体的数字对象以及 True 和 False 对象,层层细分,最终是具体的一个个对象,它们都是对象。这也是为什么称 Python 一切皆对象的原因。

类似地,对数据,因为要实现的目的不同,将装数据的 “容器” 分为字符串,列表,元组,集合,字典等类型的对象。

函数抽象出函数对象,模块又抽象出模块对象…… 甚至连空都是对象(None)。

具体的对象,称为实例,它归属于某个类。而所有的类,又都是 object 对象的子类。各种类(类型),又统一归为类型对象(type)。

type(1), isinstance(1,int) # 1 是 int 类的实例
(int, True)
issubclass(int,object) # int 类是 object 的子类
True
type(int), isinstance(int,type) # int 是 type 的实例
(type, True)

按照面向对象的思想,我们可以定义一个 “人” 类对象,然后很方便地给这个 “人” 加上现有的各种函数,类,以及模块的功能,造出更多更多有各种特异功能的 “人”。这是不是非常伟大的设计思想呢?

类定义

类是用来创建用户定义对象的模板,是抽象的对象(类对象)。例如我们说鱼类,指的就是一类抽象的对象,而具体到武昌鱼,鲈鱼,鲫鱼…… 就是具体的对象。

定义了一个类,就可以用这个模块来创建它的具体对象(实例)。类定义的详细语法规则见 class 定义类

创建一个类,需要继承另一个类。新创建的类是子类,继承的类是基类。如不指定,创建类默认继承自所有类的基类 object。

下面是一个没有任何自定义属性的简单类:

class A:
    pass

A
__main__.A
type(A)
type
A.__bases__ # 查看基类
(object,)

继承一个指定的类,可以对它进行定制化开发。需要注意参数 self, 它是约定名称(可自定义但不推荐),它就代表类创建的实例对象自身。

例如创建一个自定义的列表类,增加 add() 方法:

class Mylist(list):
    def add(self,value):
        self.append(value)
        
lst = Mylist('123') # 创建实例赋值给 lst
lst.add(4) # lst 调用 add 方法,self 参数就是 lst
lst
['1', '2', '3', 4]

或者自定义一个字典,当访问的键不存在时,不报错,而是返回 None(__missing__() 是用来定义字典子类时找不到键如何处理的魔法方法):

class Mydict(dict):
    def __missing__(self, key):
        return 

d = Mydict(a=1,b=2)
d, d['c']
({'a': 1, 'b': 2}, None)

我们也可以自定义任意的类,其中 __init__() 方法是创建实例时用来初始化实例对象的魔法方法,可以用它来增加一些自定义属性。没有该方法,创建实例时,将自动调用基类的该方法完成初始化。

例如定义一个 “人” 类:

class Person:
    '这是人类'
    # 定义人的属性
    def __init__(self, name):
        self.name = name
        self.ears = '耳朵'
        self.brain = '大脑'
        self.hands = '双手'
    
    # 人有生活
    def live(self):
        print(f'{self.name}{self.ears}'
              '听着音乐,'
              '享受美好生活。')
        
    # 还有工作
    def work(self):
        print(f'{self.name}{self.brain}和'
              f'{self.hands}'
               '勤劳致富。')
        
# 造人
xm = Person('小明')
xz = Person('小张')
xm.live() # 小明在享受生活
xz.work() # 小张在努力工作
小明用耳朵听着音乐,享受美好生活。
小张用大脑和双手勤劳致富。

实例

实例是调用类对象创建的具体对象。例如调用内置类型 int,创建的所有整数,都是 int 类的实例。可通过内置函数 isinstance() 进行实例检查。

int('1')
1
isinstance(1,int)
True

Python 中所有的实例对象,都是 object 的实例。所有的类都是 object 的子类,也被视为 object 的实例;所有的类也被视为默认的元类(创建类的类)type 的实例。

如果一个实例对象应该视为某个类的实例,可以通过魔法方法 __instancecheck__() 来重载 isinstance() 函数,自定义实例检查行为。

isinstance(1,object), isinstance([],object)
(True, True)
isinstance(int,object)
True
isinstance(int,type)
True

下面定义一个 “人” 类,来对实例进行说明:

class Person:
    '这是人类'
    # 定义人的属性
    def __init__(self, name):
        self.name = name
        self.ears = '耳朵'
        self.brain = '大脑'
        self.hands = '双手'
    
    # 人有生活
    def live(self):
        print(f'{self.name}{self.ears}'
              '听着音乐,'
              '享受美好生活。')
        
    # 还有工作
    def work(self):
        print(f'{self.name}{self.brain}和'
              f'{self.hands}'
               '勤劳致富。')
        
# 造人
xm = Person('小明')
xz = Person('小张')

上述 “人” 类对象 Person 是类对象,调用类对象创建的 xm 和 xz,是两个具体的 “人”,是实例对象。

Person
__main__.Person
xm
<__main__.Person at 0x24cf9ef6d68>
xz
<__main__.Person at 0x24cf9ef6d30>
isinstance(xm, Person), isinstance(xz, Person)
(True, True)
isinstance(Person, object), isinstance(Person, type)
(True, True)

对象

对象是 Python 中对数据的抽象。Python 程序中的所有数据都是由对象或对象间关系来表示的。

例如计算 x + y 就有对象 xy+ 其实是调用了 __add__ 方法对象。

[1,2] + [3,4]
[1, 2, 3, 4]
list.__add__
<slot wrapper '__add__' of 'list' objects>
[1,2].__add__([3,4])
[1, 2, 3, 4]

每个对象都有各自的编号、类型和值。一个对象被创建后,它的编号就绝不会改变,可以将其理解为该对象在内存中的地址。

id([1,2]), type([1,2]) # 实例对象 [1,2] 的编号 和 类型
(2229938322504, list)
id(list), type(list) # 类对象 list 的编号 和 类型
(140736641645872, type)

对象的值在 Python 中是一个相当抽象的概念:

  • 对象的值并没有一个规范的访问方法;
  • 对象的值并不要求具有特定的构建方式,例如由其全部数据属性组成等;
  • 比较运算符实现了一个特定的对象的值概念,可以认为正是通过实现对象比较,间接地定义了对象的值。
# 列表对象 [1,2] 和类对象 list 
# 是两个不同对象,值是不相等的
[1,2] == list 
False

具有固定值的对象为不可变对象,例如数字,字符串,元组(包含的对象集不可变),相等的两个值,可能会也可能不会指向同一个对象,看具体实现。

# 元组的对象集固定不可变
t = ([1,2],(3,4))
print(id(t[0]),id(t[1]),t)
del t[0][:]
print(id(t[0]),id(t[1]),t)
2229937078536 2229937013960 ([1, 2], (3, 4))
2229937078536 2229937013960 ([], (3, 4))
# 同一个对象 1
print(id(1))
print(id(1))
140736642126656
140736642126656
# 两个不同对象 1000
print(id(1000))
print(id(1000))
2229938479344
2229938479248

对象的编号保持不变,可以改变值的对象为可变对象。对于可变对象,分别创建两个相等的值,一定是不同对象。

# 可变对象 a,值改变了还是 a
a = [1,2]
print(id(a), a)
del a[:]
print(id(a), a)
2229937082056 [1, 2]
2229937082056 []
# 类对象 A,增加属性 a 还是 A
class A:pass
print(id(A), A)
A.a = 0
print(id(A), A)
2229917928424 <class '__main__.A'>
2229917928424 <class '__main__.A'>
a = []
b = []
print(id(a))
print(id(b))
a == b
2229937416840
2229937416648


True

类或函数对象的哈希值基于其 id,创建两个看起来完全一样的类或函数,id 是不一样的,而可哈希对象必须具有相同的哈希值,比较结果才会相同。

def f():pass
print(id(f))
a = f

def f():pass
print(id(f))
b = f
a == b
2229938263992
2229938264128


False

名称

名称(标识符)用于指代对象。当名称被绑定到一个对象时,对该名称求值将返回相应对象。当名称未被绑定时,尝试对其求值将引发 NameError 异常。Python 的保留字或称关键字是特殊的名称,不可被用作普通名称,例如不可用做变量名,函数名,模块名。

# 将 1 绑定名称 a
a = 1
a
1
# 将自定义类对象绑定到名称 A
class A:
    pass
A
__main__.A
# 将模块 random 绑定到名称 r
import random as r
r
<module 'random' from 'F:\\anaconda\\lib\\random.py'>
# 迭代的每一项循环绑定到名称 i
for i in 'xue':
    display(i)

i
'x'

'u'

'e'

'e'
# 未绑定的名称 m
m
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-4-2469ef96c490> in <module>
      1 # 未绑定的名称 m
----> 2 m


NameError: name 'm' is not defined
help('keywords') # 保留字
Here is a list of the Python keywords.  Enter any keyword to get more help.

False               class               from                or
None                continue            global              pass
True                def                 if                  raise
and                 del                 import              return
as                  elif                in                  try
assert              else                is                  while
async               except              lambda              with
await               finally             nonlocal            yield
break               for                 not                 
False = 0
  File "<ipython-input-9-223dbc74e028>", line 1
    False = 0
    ^
SyntaxError: cannot assign to False

检查一个名称是否有效可用字符串方法 str.isidentifier():

'for'.isidentifier()
True
'my_name'.isidentifier()
True
'函数'.isidentifier()
True

普通名称命名规则:

  • 可以使用大部分非标点符号的 Unicode 字符,但请使用 ASCII 范围内 (U+0001..U+007F) 的字符;
  • 习惯使用大写和小写字母 A 至 Z,下划线 _ 以及数字 0 至 9,但不可以数字打头;
  • 长度没有限制,对大小写敏感。
数字一 = 1
数字一
1
def 函数():
    pass
函数
<function __main__.函数()>

命名模式以下划线字符打头和结尾,有特殊含义的名称(* 代表任意数量可用名称字符):

  • _* 不会被 from module import * 导入。特殊标识符 _ 在交互式解释器中被用来存放最近一次求值结果;它保存在 builtins 模块中。当不处于交互模式时,_ 无特殊含义也没有预定义。

  • __*__ 系统定义的名称,这些名称是由解释器及其实现(包括标准库)定义的特殊属性和方法名称。未来的 Python 版本中还将定义更多此类名称。任何情况下任何不遵循文档所显式指明的 __*__ 名称使用方式都可能导致无警告的错误。

  • __* 类的私有名称。这种名称在类定义中使用时,会以一种混合形式重写以避免在基类及派生类的 “私有” 属性之间出现名称冲突。例如,出现在一个名为 Ham 的类中的名称 __spam 会被转换为 _Ham__spam。如果类名仅由下划线组成,则不会进行转换。

b = 100
b
100
_
100
dir(object)
['__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']
class B:
    __b = 0
dir(B)
['_B__b',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

del 语句可删除名称绑定:

c = 3
print(f'c={c}')
del c
c
c=3


---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-23-5ba97d2f3254> in <module>
      2 print(f'c={c}')
      3 del c
----> 4 c


NameError: name 'c' is not defined

变量

Python 中绑定对象的名称即为变量。

人们称呼模块、类、函数和方法对象的名称时,习惯直接指代对象本身,而不是把它们当作变量,例如 random,int,print 等,当将它们绑定到另外的名称时,才称新名称为变量(或化名)。

# 函数名称 f 绑定自定义函数对象
def f(x, func): 
    print(func(x))
    
a = '1231' # 变量 a
b = [int,list,set] # 变量 b
c = f # 名称 f 起别名 c

for i in b: # 变量 b 中的项循环赋值给变量 i
    c(a,i)
1231
['1', '2', '3', '1']
{'3', '1', '2'}

如果名称绑定在一个代码块中,则为该代码块的局部变量,除非声明为 nonlocal 或 global。

如果名称绑定在模块层级,则为全局变量。(模块代码块的变量既为局部变量又为全局变量。)

如果变量在一个代码块中被使用但不是在其中定义,则为自由变量。

# 第一个代码块中全局变量 n
n = 100 
# 第二个代码块
# random 模块中的全局变量 randint,
# 在此为局部变量,引用后赋值给全局变量 r
import random
r = random.randint
# 第三个代码块中 x 为局部变量
# n 和 r 不在该代码块中定义,在该代码块为自由变量
def f(x):
    print(r(x,n))
    
f(1)
22

如果代码块中定义了一个局部变量,则其作用域包含该代码块。如果定义发生于函数代码块中,则其作用域会扩展到该函数所包含的任何代码块,除非有某个被包含代码块引入了对该名称的不同绑定。

m = 0
n = 1 # 全局变量 n
def f():
    n = 2 # 局部变量 n
    print(m,n)
f()
0 2

改变变量作用域,详见 global 语句nonlocal 语句

类变量和实例变量:

  • 在类中定义,仅在类层级修改的变量为类变量,实例也可访问类变量;
  • self.name 命名的变量为实例变量,类不能访问。
class A:
    a = 1 # 类变量
    def __init__(self):
        self.a = 100 # 实例变量
        self.b = 2
        
a = A()
A.a, a.a, a.b
(1, 100, 2)
A.b # 类 A 不能访问它的实例的变量 b
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-11-f92f3bcfde9d> in <module>
----> 1 A.b # 类 A 不能访问它的实例的变量 b


AttributeError: type object 'A' has no attribute 'b'

属性

属性指关联到一个对象的值,可以使用点号表达式通过其名称来引用。

可以使用 dir() 函数查看任意对象的属性。

如果属性是可调用对象(例如类,函数,方法等),引用之后可直接调用,也可先赋值给变量再调用。

# 查看 list 对象的属性
dir(list)
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__delitem__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__gt__',
 '__hash__',
 '__iadd__',
 '__imul__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__reversed__',
 '__rmul__',
 '__setattr__',
 '__setitem__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'append',
 'clear',
 'copy',
 'count',
 'extend',
 'index',
 'insert',
 'pop',
 'remove',
 'reverse',
 'sort']
# 通过名称 __add__ 引用属性
list.__add__ 
<slot wrapper '__add__' of 'list' objects>
# 直接调用属性
list.__add__([1,2],[3,4])
[1, 2, 3, 4]
# 赋值给变量再调用
la = list.__add__
la([1,2],[3,4])
[1, 2, 3, 4]

有时也只将具体的值称为属性从而与抽象的值(类,函数,方法等对象的值)区分开来。犹如将一个人有身体和四肢的属性和这个人会唱歌跳舞的属性区分开来。

import pandas as pd
pd.Series # pandas 模块的属性 Series 类
pandas.core.series.Series
import math
math.pi # math 模块的属性圆周率
3.141592653589793
class A:
    a = 1
    def f(self):
        print(self)
        
a = A()
A.a, a.a # 类 A 及其实例的数据属性 a
(1, 1)
A.f, a.f # 类 A 及其实例的方法属性 f
(<function __main__.A.f(self)>,
 <bound method A.f of <__main__.A object at 0x000001B569DC0860>>)
# 方法属性的等价调用
# 实例直接调用,第一个参数 self 就是它自身
a.f()
# 类直接调用,则需要传入实例作为参数
A.f(a)
<__main__.A object at 0x000001B569DC0860>
<__main__.A object at 0x000001B569DC0860>

属性名以一个下划线开头的属性应该视为 “私有” 属性,但可以直接访问。

属性名以两个下划线开头,非两个下划线结尾的属性,在模块中视为 “私有” 属性,但可以直接访问;在类中属于私有属性,这种名称在类定义中使用时,会以一种混合形式重写以避免在基类及派生类的 “私有” 属性之间出现名称冲突。类的私有属性并非不可访问(使用重写名称可访问),全靠自觉。

class A:
    _a = 1
    def __f():
        pass
dir(A)
['_A__f',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_a']
A._a
1
A._A__f
<function __main__.A.__f()>

方法

方法指在类内部定义的函数。但并不严格要求一定要在类内部定义。

def f(self):
    pass

class A:
    f = f
A().f
<bound method f of <__main__.A object at 0x00000290DC56E198>>

下列方法看起来是模块中的函数,其实是模块中,类实例方法重新赋值的名称:

from random import randint
type(randint)
method
import random
# randint 完整路径
random.Random.randint

# 例如重新赋值后即可直接调用
r = random.Random()
randint = r.randint
randint(0,3)
2

普通方法(第一个参数通常命名为 self)如果作为该类的实例的一个属性来调用,方法将会获取实例对象作为其第一个参数。

# list 类的方法 append
help('list.append')
Help on method_descriptor in list:

list.append = append(self, object, /)
    Append object to the end of the list.
# list 的实例 [] 调用,
# 方法将会获取实例对象 [] 作为其第一个参数
a = []
a.append(1)
a
[1]
# 等价于
a = []
list.append(a,1)
a
[1]

类方法(第一个参数通常命名为 cls)则无论是类或是实例调用,方法都将获取类对象作为其第一个参数。类方法定义详见 classmethod 封装函数为类方法

class A:
    @classmethod
    def f(cls,x):
        print(cls,x)
        
print(f'A = {A}')        
A.f('类方法')
A().f('类方法')
A = <class '__main__.A'>
<class '__main__.A'> 类方法
<class '__main__.A'> 类方法

静态方法则不会接收隐式的第一个参数。调用它需要正常传递参数。静态方法详见 staticmethod 封装函数为静态方法

class A:
    @staticmethod
    def in_print(value):
        print(value)
        
a = A()
a.in_print('静态方法')
A.in_print('静态方法')
静态方法
静态方法

特殊方法(也称魔术方法):一种由 Python 隐式调用的方法,用来对某个类型执行特定操作例如相加等等。这种方法的名称的首尾都为双下划线。可以通过方法重载,对某个类型定义特定操作。

# 1 + 2 操作其实是隐式调用了 __add__
1 + 2
3
(1).__add__(2)
3
# 列表没有 “-” 操作符,自定义一个
class Mlist(list):
    def __sub__(self, other):
        return list(set(self) - set(other))
    
a = Mlist('123')
b = Mlist('13')
a, b, a - b
(['1', '2', '3'], ['1', '3'], ['2'])

命名空间

命名空间是存放变量的场所。命名空间有局部、全局和内置的,还有对象中的嵌套命名空间。不同命名空间中的变量没有关系。

命名空间通过防止命名冲突来支持模块化。例如,函数 builtins.open 与 os.open 可通过各自的命名空间来区分。

# 函数的局部命名空间,在函数调用时创建
def f():
    x = '函数 f 命名空间中的变量 x'
    print(x)
# 全局命名空间
x = '全局命名空间中的变量 x'
# 调用函数 f,与全局命名空间中的 x 无关
f()
函数 f 命名空间中的变量 x
# 模块 random 导入创建它自己的局部命名空间
import random
# 定义一个全局变量 randint
def randint():
    print('全局 randint')
# random 局部命名空间中的 randint 
# 与全局变量 randint 无关
randint()
random.randint(0,3)
全局 randint


1
# 上述定义的变量 f, x, randint,导入的变量 random,
# 都存放在了当前全局命名空间中
f, x, random, randint
(<function __main__.f()>,
 '全局命名空间中的变量 x',
 <module 'random' from 'C:\\ProgramData\\Anaconda3\\lib\\random.py'>,
 <function __main__.randint()>)

内置命名空间是在 Python 解释器启动时创建,存放的变量包括内置函数、异常等。

# 全局命名空间中定义变量 str,
# 将屏蔽内置命名空间中的 str
def str(x):
    return f'x = {x}'
str(123)
'x = 123'
# 但可以在 builtins 中继续调用
import builtins

builtins.str(123)
'123'

递归函数,每次递归调用,都会有一个新的命名空间。

def f(x):
    if x > 0:
        print(f'x={x}调用,x-1={x-1}继续调用')
    if x == 0:
        print(f'x={x},调用结束')
    else:
        # 以 x-1 作为参数调用函数 f
        return f(x-1)
f(3)
x=3调用,x-1=2继续调用
x=2调用,x-1=1继续调用
x=1调用,x-1=0继续调用
x=0,调用结束

嵌套的命名空间:

a = '全局变量 a'
class A:
    a = '类变量 a'
    def f(self):
        a = '函数局部变量 a'
        return a

print(a)
print(A.a)
print(A().f())
全局变量 a
类变量 a
函数局部变量 a

作用域

作用域定义了一个代码块中名称的可见性。如果代码块中定义了一个局部变量,则其作用域包含该代码块。如果定义发生于函数代码块中,则其作用域会扩展到该函数所包含的任何代码块,除非有某个被包含代码块引入了对该名称的不同绑定。

当一个名称在代码块中被使用时,会由包含它的最近作用域来解析。对一个代码块可见的所有这种作用域的集合称为该代码块的环境。

一个作用域是一个 命名空间 可直接访问的 Python 程序的文本区域。这里的 “可直接访问” 意味着对名称的非限定引用(限定引用指点号表达式例如 math.pi)会尝试在命名空间中查找名称。

# 全局变量作用域为当前模块
a = '全局变量'
def f():
    # print 内置名称作用域包含全局
    print(f'print 函数的命名空间可以访问:{a}')
    return f'f 的命名空间可以访问:{a}'

print(a)
print(f())
全局变量
print 函数的命名空间可以访问:全局变量
f 的命名空间可以访问:全局变量
# 全局变量作用域为当前模块
# 但被包含命名空间引入了同名的局部变量
a = '全局变量'
def f():
    # f 中定义的局部变量
    # 作用域为函数内部
    a = '局部变量'
    print(f'print 函数的命名空间可以访问:{a}')
    return f'f 的命名空间可以访问:{a}'

print(a)
print(f())
全局变量
print 函数的命名空间可以访问:局部变量
f 的命名空间可以访问:局部变量

虽然作用域是静态地确定的,但它们会被动态地使用。嵌套作用域的搜索顺序:

  • 最先搜索最内部作用域包含的局部名称
  • 从最近的封闭作用域开始搜索作用域包含的名称
  • 倒数第二个作用域包含当前模块的全局名称
  • 最外面的作用域(最后搜索)是包含内置名称的命名空间
# 搜索最内部作用域名称 str
# 屏蔽上层作用域
def f():
    str = 0
    def f1():
        str = 1
        return str
    return f1()

f(), str
(1, str)
# 搜索最近封闭作用域名称 str
# 屏蔽上层作用域
def f():
    str = 0
    def f1():
        return str
    return f1()

f(), str
(0, str)
# 搜索当前模块作用域名称 str
# 屏蔽上层作用域
str = '当前模块str'
def f():
    def f1():
        return str
    return f1()

f()
'当前模块str'
# 搜索最外面作用域名称 str
# 删除前面对 str 的绑定
del str
# 注意,运行多次会将内置名称 str 都删除

def f():
    def f1():
        return str
    return f1()

f()
str

可见,当前模块中的全局名称,最好不要和内置名称相同,它会屏蔽掉内置名称,从而不可以直接使用内置功能。内置名称如下:

import builtins
dir(builtins)
['ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BaseException',
 'BlockingIOError',
 'BrokenPipeError',
 'BufferError',
 'BytesWarning',
 'ChildProcessError',
 'ConnectionAbortedError',
 'ConnectionError',
 'ConnectionRefusedError',
 'ConnectionResetError',
 'DeprecationWarning',
 'EOFError',
 'Ellipsis',
 'EnvironmentError',
 'Exception',
 'False',
 'FileExistsError',
 'FileNotFoundError',
 'FloatingPointError',
 'FutureWarning',
 'GeneratorExit',
 'IOError',
 'ImportError',
 'ImportWarning',
 'IndentationError',
 'IndexError',
 'InterruptedError',
 'IsADirectoryError',
 'KeyError',
 'KeyboardInterrupt',
 'LookupError',
 'MemoryError',
 'ModuleNotFoundError',
 'NameError',
 'None',
 'NotADirectoryError',
 'NotImplemented',
 'NotImplementedError',
 'OSError',
 'OverflowError',
 'PendingDeprecationWarning',
 'PermissionError',
 'ProcessLookupError',
 'RecursionError',
 'ReferenceError',
 'ResourceWarning',
 'RuntimeError',
 'RuntimeWarning',
 'StopAsyncIteration',
 'StopIteration',
 'SyntaxError',
 'SyntaxWarning',
 'SystemError',
 'SystemExit',
 'TabError',
 'TimeoutError',
 'True',
 'TypeError',
 'UnboundLocalError',
 'UnicodeDecodeError',
 'UnicodeEncodeError',
 'UnicodeError',
 'UnicodeTranslateError',
 'UnicodeWarning',
 'UserWarning',
 'ValueError',
 'Warning',
 'WindowsError',
 'ZeroDivisionError',
 '__IPYTHON__',
 '__build_class__',
 '__debug__',
 '__doc__',
 '__import__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'abs',
 'all',
 'any',
 'ascii',
 'bin',
 'bool',
 'breakpoint',
 'bytearray',
 'bytes',
 'callable',
 'chr',
 'classmethod',
 'compile',
 'complex',
 'copyright',
 'credits',
 'delattr',
 'dict',
 'dir',
 'display',
 'divmod',
 'enumerate',
 'eval',
 'exec',
 'filter',
 'float',
 'format',
 'frozenset',
 'get_ipython',
 'getattr',
 'globals',
 'hasattr',
 'hash',
 'help',
 'hex',
 'id',
 'input',
 'int',
 'isinstance',
 'issubclass',
 'iter',
 'len',
 'license',
 'list',
 'locals',
 'map',
 'max',
 'memoryview',
 'min',
 'next',
 'object',
 'oct',
 'open',
 'ord',
 'pow',
 'print',
 'property',
 'range',
 'repr',
 'reversed',
 'round',
 'set',
 'setattr',
 'slice',
 'sorted',
 'staticmethod',
 'str',
 'sum',
 'super',
 'tuple',
 'type',
 'vars',
 'zip']

创建实例

直接调用类对象,即可创建该类的实例对象。

int() # 调用 int 类
0
list('123') # 传参调用 list 类
['1', '2', '3']
class A:
    def __init__(self, name):
        self.name = name
        
    def __repr__(self):
        return self.name
    
A('实例1'), A('实例2') # 调用自定义类
(实例1, 实例2)

实例对象是由特殊方法 __new__() 创建,__init__() 定制完成。两个方法是隐式地完成创建和定制的,当然也可以显式地创建并定制。

class A:
    def __init__(self):
        self.name = '实例'
        
A # 类对象 A
__main__.A
# 显式地创建类 A 的一个实例 a
a = object.__new__(A)
a
<__main__.A at 0x20d23e95d30>
# 未初始化,无属性
a.name
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-36-b3a4d04d98fc> in <module>
      1 # 未初始化,无属性
----> 2 a.name


AttributeError: 'A' object has no attribute 'name'
# 也可以使用 a.__init__(), 
# 它会自动将实例作为第一个参数完成初始化
A.__init__(a)
a
<__main__.A at 0x20d23e95d30>
a.name
'实例'

属性操作

Python 是动态语言,对象的属性不仅可以查看、访问、调用,还可以动态地增、删、改。

下面定义一个没有自定义属性的类 A 举例:

dir() 函数查看属性,双下划线 __ 包围的属性是继承自 object 的特殊属性:

# 定义一个类,查看属性
class A:
    pass

dir(A)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']
# 访问属性 __class__ 相当于调用 type() 函数
A.__class__, A().__class__
(type, __main__.A)

给类 A 及其实例动态添加属性,属性可以是数据属性和方法。类层级添加的属性,将作为所有实例的属性;实例添加的属性,只有对应的实例能访问。

# 添加数据属性
a1 = A()
a2 = A()
A.a = '类变量'
a1.x = 'a1 的属性'
a2.y = 'a2 的属性'
# 类层级添加方法
def f(self):
    print(self.a)

A.f = f
# 类 A 调用刚添加的属性
A.f(a1)
A.f(a2)
类变量
类变量
# 实例调用刚添加的属性
a1.f()
a2.f()
类变量
类变量
# 实例 a1 没有 y 属性
# 同理,a2 没有 x 属性
a1.y 
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-44-178a7ac627a0> in <module>
      1 # 实例 a1 没有 y 属性
      2 # 同理,a2 没有 x 属性
----> 3 a1.y


AttributeError: 'A' object has no attribute 'y'

实例直接添加一个函数作为属性,将不会隐式地将自身作为第一个参数,而是和正常函数一样使用:

def p(self):
    print(self)

a1.p = p
a1.p('正常传参')
a1.p()
正常传参


---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-45-4ebdece61250> in <module>
      4 a1.p = p
      5 a1.p('正常传参')
----> 6 a1.p()


TypeError: p() missing 1 required positional argument: 'self'

如果要通过实例添加实例方法(第一个参数 self 即是自身的方法),可以通过 types 模块添加。添加的属性,类不可访问,只有对应的实例可访问:

from types import MethodType

# 改变了 a1 的上述 p 属性
a1.p = MethodType(p, a1)
a1.p()
<__main__.A object at 0x000001D40578D730>
A.p
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-47-94cb580acfb0> in <module>
----> 1 A.p


AttributeError: type object 'A' has no attribute 'p'
a2.p
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-48-29321b2aa581> in <module>
----> 1 a2.p


AttributeError: 'A' object has no attribute 'p'

删除属性使用 del 语句:

del a1.p

a1.p()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-49-9cf9b7e7dc86> in <module>
      1 del a1.p
      2 
----> 3 a1.p()


AttributeError: 'A' object has no attribute 'p'

方法操作

一个从父类继承过来的方法,如果不满足子类的需求,可以进行重写,重写的方法将屏蔽父类的方法,但可以显示地调用,或使用 super() 委托给父类调用,实现父类、子类的方法都可调用。

class A:
    def f(self,x):
        y = x + x
        print(y)
class B(A):
    def f(self,x):
        y = (x + x)**2 
        print(y)

b = B()
b.f(2)
16
# 显示地调用
A.f(b, 2)
4
class A:
    def f(self,x):
        y = x + x
        print(y)
class B(A):
    def f(self,x):
        super().f(x)
        y = (x + x)**2 
        print(y)

b = B()
b.f(2)
4
16

还可使用装饰器修改方法,或添加丰富功能等。

# 将方法定义为静态方法
class C:
    @staticmethod
    def f(value):
        print(value)
c = C()
c.f('必须传参调用')
必须传参调用
# 将私有属性定义为只读,
# 直接用不带下划线的名称访问
class C:
    def __init__(self):
        self.__name = '私有属性'
    @property
    def name(self):
        return self.__name
c = C()
c.name
'私有属性'
# 尝试修改不被允许
c.name = 0
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-11-c0f354f546f5> in <module>
      1 # 尝试修改不被允许
----> 2 c.name = 0


AttributeError: can't set attribute

用特殊方法定制类:

class D:
    pass

d = D()
bool(d)
True
# 上述类的实例逻辑值检查为 True
# 定义为 False
class D:
    def __bool__(self):
        return False
d = D()
bool(d)
False
# 让数字字符串也可以相减
class Mystr(str):
    def __sub__(self, other):
        return str(float(self) - float(other))
m = Mystr('123')
n = Mystr('3.14')
m, n, m - n
('123', '3.14', '119.86')

类继承

所有的类都继承自 object。被继承的类称为基类(或父类,超类),继承者称为子类。

对于多数应用来说,在最简单的情况下,你可以认为搜索从父类所继承属性的操作是深度优先、从左至右的,当层次结构中存在重叠时不会在同一个类中搜索两次。

class A():
    def show(self):
        print('A')

class B(A):
    pass

class C():
    pass

class D():
    def show(self):
        print('D')

class E(C, B, D):  # C -> B -> A -> D
    pass

e = E()
e.show()
A

真实情况比这个更复杂一些;方法解析顺序会动态改变以支持对 super() 的协同调用。这种方式在某些其他多重继承型语言中被称为 后续方法调用,它比单继承型语言中的 super 调用更强大。

动态改变顺序是有必要的,因为所有多重继承的情况都会显示出一个或更多的菱形关联(即至少有一个父类可通过多条路径被最底层类所访问)。例如,所有类都是继承自 object,因此任何多重继承的情况都提供了一条以上的路径可以通向 object。为了确保基类不会被访问一次以上,动态算法会用一种特殊方式将搜索顺序线性化,保留每个类所指定的从左至右的顺序,只调用每个父类一次,并且保持单调(即一个类可以被子类化而不影响其父类的优先顺序)。

总而言之,这些特性使得设计具有多重继承的可靠且可扩展的类成为可能。

一个基类如果有 __init__() 方法,则其所派生的类如果也有 __init__() 方法,就必须显式地调用它以确保实例基类部分的正确初始化:

class A:
    def __init__(self):
        self.a = 'A'
    
    def f(self):
        print(self.a)
        
class B(A):
    def __init__(self):
        self.b = 'B'

b = B()
b.f() # 基类未初始化,属性 a 不可调用
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-5-cda60db4451f> in <module>
     11 
     12 b = B()
---> 13 b.f()


<ipython-input-5-cda60db4451f> in f(self)
      4 
      5     def f(self):
----> 6         print(self.a)
      7 
      8 class B(A):


AttributeError: 'B' object has no attribute 'a'
A.__init__(b) # 将实例 b 传给 A 初始化
b.f()
A
# 或者直接用 super()
class A:
    def __init__(self):
        self.a = 'A'
    
    def f(self):
        print(self.a)
        
class B(A):
    def __init__(self):
        super().__init__()
        self.b = 'B'

b = B()
b.f()
A