Python类
类的基本概念
创建一个类:
class Person:
"""
This is a sample of class
"""
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
def color(self, color):
d = {}
d[self.name] = color
return d
解释:class Person,声明创建一个名为“Person”的类。类的名称一般用大写字母开头。在Python3中所有的类都是object的子类,但是可以不用显式写出来,如果要继承其他非object的父类时,要在类名后面用括号跟上父类名字,即class Person(FatherClass)。
类里面的代码是三个函数,但是它们的参数中都有self,这是和Python中的函数不一样的地方,类中的函数被称作“方法”。方法的参数列表中必须包含self参数,并且默认作为第一个参数。
def __init__(self, name),它是一个特殊的方法,方法名用双下划线开头和结尾,它被称为”初始化方法“,对这个类进行初始化,让这个类有一个基本的面貌。self.name = name的含义是建立实例的一个属性,这个属性的名字是name(指的是第一个name),和参数name的名字是一样的,参数name是在这个类实例化时传入的,注意二者的区别。特别注意__init__()没有return语句
def get_name(self)和def color(self, color)是类里面的另外两个方法,除了第一个参数是self外,其它和函数并没有区别。
类的实例对象:
if __name__ == "__main__":
girl = Person("xiaohong")
print(girl.name)
name = girl.get_name()
print(name)
her_color = girl.color("white")
print(her_color)
解释:girl = Person(“xiaohong”)创建了一个girl实例,创建实例的过程就是调用类Person()。创建的过程中首先执行初始化方法__init__(),self是默认参数,不需要传值,参数name获得了字符串"xiaohong"的引用,通过参数name得到实例属性self.name=“xiaohong”。
girl.get_name()通过实例girl来调用get_name方法,这个方法返回了实例属性self.name的值,所以print(name)的结果是xiaohong。girl.color(“white”)实例girl调用color方法的时候传入一个参数"white",因为类中定义该方法的时候参数列表里有color,print(her_color)的结果是{‘xiaohong’:‘white’}。
类和实例的关系:
类提供默认行为,是实例的工厂(From Learning Python)
类属性和实例属性
类属性
class Girl:
breast = 90
breast就是类Girl的属性。这种属性本质上就是类中的变量,它的值不依赖于任何实例,只是由类中所写的变量赋值语句所确定,所以这个类属性也叫静态变量或静态数据。对这个类属性可以进行修改、删除。
Girl.breast = 100 #修改类属性
del Girl.breast #删除类属性
Girl.height = 165 #增加类属性
使用dir()查看类Girl的属性和方法。
In [5]: dir(Girl)
Out[5]:
['__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__',
'breast']
双下划线开头和结尾的是类的特殊属性,通常情况下特殊属性不需要修改。几种特殊属性的含义如下:
- C.__name__:以字符串的形式返回类的名字
- C.__doc__:显示类的文档
- C.__base__:类C的所有父类
- C.__dict__:以字典的形式显示类的所有属性
- C.__module__:类所在的模块
In [6]: Girl.__dict__
Out[6]:
mappingproxy({'__dict__': <attribute '__dict__' of 'Girl' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'Girl' objects>,
'breast': 90})
In [7]: Girl.__base__
Out[7]: object
In [8]: Girl.__module__
Out[8]: '__main__'
创建实例
调用类即可创建实例。
In [2]: Girl()
Out[2]: <__main__.Girl at 0xc9ab550>
In [3]: xiaohong = Girl()
In [4]: xiaohong
Out[4]: <__main__.Girl at 0xc9ab2e8>
仅仅写Girl()也是创建了一个实例对象,xiaohong = Girl()意思是变量xiaohong与实例对象Girl()建立了引用关系,类似于赋值语句x = 3。创建实例的过程如下:
- 创建实例对象。
- 检查是否有__init__()方法。如果没有,则返回实例对象;有则进入下一步。
- 调用__init__()方法,将实例对象作为第一个参数self传递进去。
再次注意下__init__()初始化方法,它的第一个参数必须是self,不能有return语句。
实例属性
In [5]: class A:
...: x = 1
...:
In [6]: foo = A() #实例化
In [7]: A.x #类属性
Out[7]: 1
In [8]: foo.x #实例属性
Out[8]: 1
In [9]: foo.y = 2 #新建实例属性
In [10]: foo.y
Out[10]: 2
In [11]: foo.x += 1 #修改实例属性
In [12]: foo.x
Out[12]: 2
In [13]: del foo.x #删除修改后的实例属性
In [14]: foo.x
Out[14]: 1
In [15]: A.x += 1 #修改类属性
In [16]: A.x
Out[16]: 2
In [17]: foo.x #实例属性也跟着改变
Out[17]: 2
实例属性和类属性最大的不同在于,实例属性可以随意更改。这不会对类属性产生影响,因为类属性和类绑定。实例属性可以从类属性处获得比如foo.x,也可以自己创建比如foo.y。对实例属性的修改本质上是新建了一个新的属性foo.x,与原先的foo.x重名,覆盖了原先的foo.x,如果删除修改后的实例属性foo.x,就得到了初始的foo.x。如果修改类属性,实例属性也会跟着改变。
形象地说,就像工厂生产模型,模具(类)的形状和大小是不变的,利用模具生产出来的模型(实例)为了满足不同人群的需求,会有不同的颜色,不同的装扮,既包含了模具的形状大小等特征,又有自身的特色。如果模具的特征改变了,模型的特征也会跟着改变。
可变对象和不可变对象:可变对象所指向的内存中的值可以被改变,不可变对象所指向的内存中的值不能被改变。在Python中,数值类型(int和float)、字符串str、元组tuple都是不可变类型;而列表list、字典dict、集合set是可变类型。(详细说明见Python中的可变对象和不可变对象)
以上类属性和实例属性的关系是针对类中变量引用的是不可变对象,如果类中变量引用的是可变对象,类属性和实例属性直接修改这个对象。
关于self:
In [18]: class Person:
...: def __init__(self, name):
...: self.name = name
...: print(self)
...: print(type(self))
...:
In [19]: girl = Person("xiaohong")
<__main__.Person object at 0x000000000C9D06A0>
<class '__main__.Person'>
In [20]: girl
Out[20]: <__main__.Person at 0xc9d06a0>
In [21]: type(girl)
Out[21]: __main__.Person
由上面的内存地址可以看到,self和girl引用的是同一个实例对象,只不过self在类里面,girl在类外面。
方法
类中的方法除了第一个参数是self,本质上和函数没有区别。
如果类和外部的函数对象有联系,会造成类和函数的耦合性太强,不便于维护,此时会用到两个有些特别的方法:类方法和静态方法。
- 类方法:在类里面定义,它由装饰器@classmethod所装饰,第一个参数cls所引用的是所在类的类对象。
- 静态方法:在类里面定义,由装饰器@staticmethod所装饰,不以self为第一个参数,仅仅是一个普通的函数。在类里面使用它的时候,通过实例调用self.staticfunction。
类方法
class Foo:
lang = "Java" #类属性
def __init__(self):
self.lang = "Python" #实例属性
@classmethod
def get_class_attr(cls):#参数是含有lang属性的类
return cls.lang #此处是类属性
if __name__ == "__main__":
print(Foo.lang)
print(Foo.get_class_attr())
f = Foo()
print(f.lang)
print(f.get_class_attr())
#result: Java,Java,Python,Java
静态方法
import random
class Foo:
def __init__(self, name):
self.name = name
def get_name(self, age):
if self.select(age): #实例调用静态方法
return self.name
else:
return "the name is secret"
@staticmethod
def select(n): #静态方法
a = random.randint(1,100)
return a - n > 0
if __name__ == "__main__":
f = Foo("haha")
name = f.get_name(22)
print(name)
继承 多态 封装
(1) 继承
继承的特点是将父类的方法和属性全部承接到子类中;如果子类重写了父类的方法,就使用子类的该方法,父类的被遮盖。
一种比较贪心的情况,重写了父类的方法,但是还想继续使用父类的该方法。方法如下:
class Person:
def __init__(self, name):
self.name = name
class Girl(Person):
def __init__(self, name):
Person.__init__(self, name) #以类方法的方式调用父类中的初始化方法
self.real_name = "balala"
def get_name(self):
return self.name
if __name__ == "__main__":
hong = Girl("xiaohong")
print(hong.real_name)
print(hong.get_name())
#result: balala xiaohong
从上面的代码可以看出,直接显式地调用父类中的方法即可。但是这样写容易犯错,如果程序复杂子类和父类之间间隔很远,修改了父类的名字,很容易忘记修改子类中该父类的名字,所以作如下改进:
class Girl(Person):
def __init__(self, name):
#Person.__init__(self, name)
super().__init__(name) #python2.x为super(Girl, self).__init__(name)
self.real_name = "balala"
def get_name(self):
return self.name
不用父类的名字,而是用super,可得到同样的结果。关于super()的使用,参考Python: 你不知道的 super
当继承多个父类时,就要考虑继承顺序的问题。Python会按照特定的顺序遍历继承图,这个顺序叫方法解析顺序(Method Resolution Order, MRO),类都有一个名为__mro__的特殊属性,使用print(C._mro_)可以打印出类的继承顺序。
Python3中的继承顺序原则是“广度优先”,再深挖的话就是MRO背后的算法了,有兴趣的读者自行百度,但是MRO列表遵循下面三条准则:Python(面向对象编程4——继承顺序、封装)
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
(2) 多态
多态表示同一种行为具有不同的表现形式。如下面的lambda函数,并没有限制参数的类型,表明同一个函数可以对用于不同的对象,体现了多态。
In [4]: f = lambda x,y : x+y
In [5]: f(2,3)
Out[5]: 5
In [6]: f("Nan","jing")
Out[6]: 'Nanjing'
对于Java和Python的多态特征,区别在于Python不检查传入对象的类型。所以Java是属于“强类型”,Python属于“弱类型”。
(3) 封装
封装是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到。比如一台主机,就是一个封装起来的对象,使用者不需要知道内部的构造是怎样,只需要通过USB网口等接口与内部进行数据传输即可。
在Python的类中如果不让外部访问内部属性和方法,就要“私有化”,在准备私有化的属性(包括方法、数据)名字前面加双下划线即可。
class ProtectMe:
def __init__(self):
self.me = "gg"
self.__name = "kk" #内部属性
def __python(self): #内部方法
print("I like Python")
def code(self):
print("Which language do you like?")
self.__python()
几个特殊方法
(1) 属性拦截
- __setattr__(self,name,value): 如果要给name赋值,调用此方法。
- __getattr__(self,name): 如果name被访问,同时它不存在,调用此方法。
- __getattribute(self, name)__: 当name被访问时自动被调用,无论name是否存在。
- __delattr__(self,name): 如果要删除name,调用此方法。
重写这些方法可以有效地避免程序运行输出异常,拦截异常的属性。
(2) 迭代器
__iter__()是对象的一个特殊方法,可以说明对象是否是可迭代的。
In [19]: lst = [1,2,3,4]
In [20]: iter_lst = iter(lst)
In [21]: iter_lst
Out[21]: <list_iterator at 0xc97de10>
In [22]: hasattr(lst,"__iter__")
Out[22]: True
In [23]: hasattr(iter_lst,"__iter__")
Out[23]: True
In [24]: hasattr(lst,"__next__")
Out[24]: False
In [25]: hasattr(iter_lst,"__next__")
Out[25]: True
列表是可迭代的,但不是迭代器。有__iter__()和__next__()的对象才是迭代器
参考资料:
《跟老齐学Python轻松入门》
Python中的可变对象和不可变对象
Python: 你不知道的 super
菜鸟教程
Python’s super() considered super!
Python(面向对象编程4——继承顺序、封装)