面向对象的编程简要概括就是将要处理的问题抽象为数据和操作的集合,用类对其进行封装。其中数据和操作都称为类的属性,它们是一般是不变的。
对类进行实例化生成我们所说的对象,对象有自己的属性。对象的属性一般是个性化的,不同的对象可能具有不同的属性。同一个类的所有对象都共享类的属性。
对象属性的查找顺序为:对象自身 --> 类 --> 类的祖先类
在python中一切皆对象。
以下我们讨论python中类相关的概念和语法。
1、类的定义
class ClassName:
<statement-1>
.
.
.
<statement-N>
- 类定义必须使用class关键字
- 类名最好使用大驼峰,如ClassName, 这是一种约定
- 当输入类定义时,一个新的命名空间产生,在类中定义的所有本地变量都放入这个新的命名空间。
- 命名空间中的所有变量都是类的属性。类属性主要分为两类,数据属性(变量)和函数。
- 语句块执行完成后,生成一个类对象绑定到ClassName。ClassName的作用域由它的定义的位置决定。
python中一切皆对象,类也是对象,它是type类的实例。可以用 obj.__class__来查看一个对象是由那个类实例化而来的。
类中可以使用任何合法的语句,不过实际应用中,主要是赋值语句和函数定义。
2、类对象
下面的代码定义了一个 Chinese 类:
class Chinese:
"""A sample example class"""
nationality = 'China'
def display(self):
print(self, 'I am Chinese!', sep=', ')
for name, value in sorted(Chinese.__dict__.items(), key=lambda x: x[0]):
print(name, '==>', value)
类定义会生成一个新的命名空间,其中包含了所有在类本地定义的变量,也就是类的属性。这些属性可以在类的 __dict__属性中查看,可以将其近似等价于类的命名空间。其中 __name__形式的属性为特殊属性。
print(type(Chinese.__dict__)) # <class 'mappingproxy'>
for name, value in sorted(Chinese.__dict__.items(), key=lambda x: x[0]):
print(name, '==>', value)
# __dict__ ==> <attribute '__dict__' of 'Chinese' objects>
# __doc__ ==> A sample example class
# __module__ ==> __main__
# __weakref__ ==> <attribute '__weakref__' of 'Chinese' objects>
# display ==> <function Chinese.display at 0x1020759d8>
# nationality ==> China
- __dict__ 在类定义的时候自动生成。
- __doc__ 类定义的时候自动生成。值为类定义语句块内第一行,类的文档字符串,由三引号引用。如果没有值为None。
- __module__ 类定义所在的模块,自动添加的属性。如果是正在执行的模块,值为 __main__
- __weakref__
- display 和 nationality 用户定义属性
python中属性的引用都是通过obj.name 的形式,name为对象 obj 的属性。类中的所有属性都可以通过obj.name 的形式引用。比如该类的两个用户自定义属性:数据属性 nationality 和 函数 display:
print(Chinese.nationality) # China
print(Chinese.display) # <function Chinese.display at 0x1021759d8>
Chinese.display(1) # 1, I am Chinese!
通过类引用函数并对其进行调用,与普通的函数调用没有差异。不过,在类中定义的函数一般是给实例使用的,不建议直接通过类引用,下面会进一步说明。
3、类实例化
类的实例化使用和函数调用类似的语法。
xm = Chinese()
上面的语句创建了一个新的Chinese类的实例,并将其赋值给 xm 变量。实例有自己的命名空间:
print(xm.__dict__) # {}
可以看到 xm.__dict__ 的返回值是一个空字典,实例 xm没有自己的属性。可以直接通过 xm.name = 'xiaoming' 的形式为实例添加属性。也可以通过 del xm.name 删除该属性:
xm.name = 'xiaoming' print(xm.__dict__) # {'name': 'xiaoming'} del xm.name print(xm.__dict__) # {}
如果类的所有实例都具有某些属性,只是属性的值不同,可以通过定义实例的初始化方法,在方法中为实例添加属性。
3.1 初始化方法
类的实例化过程:
- 实例化出一个对象
- 自动调用特殊方法 __init__对产生的对象初始化。如果类中没有定义会在父类(父类的概念之后说明)中查找该方法。
我们称__init__方法为实例的初始化方法, __init__方法中不要写返回值,默认返回None。
一般在实例初始化方法中会定义实例的个性化属性,这些属性是实例私有的,可以通过实例的__dict__属性查看。实例的属性不会影响类的属性,换句话说类不能引用实例的属性。
下面是对Chinese类的扩展:
class Chinese: """A sample example class""" nationality = 'China' def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def display(self): print(self, 'I am Chinese!', sep=', ') xm = Chinese('xiaoming', 18, 'male') print(xm.__dict__) # {'name': 'xiaoming', 'gender': 'male', 'age': 18} print(Chinese.__dict__.keys()) # dict_keys(['__weakref__', '__module__', '__dict__', '__init__', 'nationality', 'display', '__doc__'])
self指代引用该方法的实例(具体实现在4.1介绍),实例化操作的参数列表化传递给__init__方法,Chinese('xiaoming', 18, 'male') --> xm.__init__('xiaoming', 18, 'male')
3.2 实例的类
查看对象的类:
- obj.__class__
- type(obj)
print(xm.__class__) print(type(xm)) # <class '__main__.Chinese'> # <class '__main__.Chinese'>
判断一个对象是不是某个类的实例:isinstance(obj, ClassName)
print(isinstance(xm, Chinese)) # True
4 实例
实例可以操作两种属性:数据属性和方法
print(xm.name, xm.age, xm.gender, sep=', ') # xiaoming, 18, male print(xm.nationality, xm.display, sep=', ') # China, <bound method Chinese.display of <__main__.Chinese object at 0x10217c898>>
有上面的代码可以看到实例不仅可以引用自己的属性,也可以引用类的属性。类属性可以被它的实例共享。
值得注意的是,实例对类的函数的引用。其返回值并不是一个函数对象而是一个"bound method"绑定的方法对象。尝试调用这个对象:
1 xm.display(1) 2 # Traceback (most recent call last): 3 # File "test.py", line 28, in <module> 4 # xm.display(1) 5 # TypeError: display() takes 1 positional argument but 2 were given 6 xm.display() # <__main__.Chinese object at 0x10217c7f0>, I am Chinese!
display函数要求传入一个位置参数,第1行给方法传入一个参数1,调用出错,错误信息告诉我们多传入了一个位置参数。
再次调用,不传参,竟然能够正常调用了!why?函数打印了传入的值,"<__main__.Chinese object at 0x10217c7f0>",表明出入的是一个Chinese对象,在本例中只能是 xm。这是怎么实现的呢?
4.1 实例方法
print(Chinese.display) print(xm.display) print(Chinese.display is xm.display) # <function Chinese.display at 0x1021759d8> # <bound method Chinese.display of <__main__.Chinese object at 0x10217c7f0>> # False
分别通过类和实例引用display属性,返回的并不是同一个对象:通过类引用返回一个函数对象;通过实例引用返回一个"bound method"绑定的方法对象。
- 方法:属于某个对象的函数。当通过对象(实例)引用一个类的属性,且该属性对应一个函数对象时,函数和实例绑定返回一个方法对象。可以通过方法对象的__self__ 和 __func__ 属性查看对应的实例和方法
method = xm.display print(method.__self__) # <__main__.Chinese object at 0x10217c7f0> print(method.__func__) # <function Chinese.display at 0x1021759d8>
- 方法对象是可调用的,方法的调用即其对应的类的函数属性的调用,特殊之处在于会将其实例作为第一个参数传给函数,等价形式为:method(*args, **kwargs) ==> method.__func__(method.__self__, **args, **kwargs)。在本例中xm.display() 等价于Chinese.display(xm)。简要概括来说就是,方法对应函数的第一个参数由python自动填充以引用实例对象。
- 在类中定义的函数一般至少有一个位置参数,第一个位置参数一般用'self'标识,指代引用函数的实例。这是一个约定,可以用任意其他合法标识符替代,为了代码的可读性,不建议这么做。
- 类中定义的函数对应到实例即为实例的方法。
很多资料中将类中的定义的函数都称为方法,虽然如此我们要知道未经过任何装饰器装饰的普通函数本质上依然是函数,只有在通过实例引用是才与实例绑定生成方法对象。
以上是相关的基本概念,下一篇讨论类的封装、继承相关的概念。