Python3面向对象编程(v3.7)

[TOC]

类和实例

类是抽象的模板,实例是根据类创建出来的一个个对象,每个对象都拥有相同的属性和方法,但各自的数据可能不相同。

Python通过class关键字定义类,类名首字母通常大写,类名后面接继承的类(如下object):

1
2
class 类名(object) :
pass

实例是通过类名+()赋值给变量获得:

1
实例名 = 类名()

可以通过init方法初始化一个实例,该方法第一个参数必须是self表示创建实例的本身:

1
2
3
4
5
6
7
8
9
10
class Person(object) :
def __init__(self,name,age) :
self.name = name
self.age = age

>>> worker = Person('张三',30)
>>> worker.name
'张三'
>>> worker.age
30

在类中定义函数和普通模块中定义函数不同的是函数第一个参数必须是实例变量self,调用时不用传入该参数。

私有成员

Python私有变量或函数,可以把变量或函数前面加上__符号,私有变量或函数只允许内部访问,不允许外部访问的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person(object) :
def __init__(self,name,age) :
self.__name = name
self.__age = age

def get_name(self) :
return self.__name

def get_age(self) :
return self.__age



>>> worker = Person('张三',30)
>>> worker.name
AttributeError: 'Person' object has no attribute 'name'
>>> worker.__age
AttributeError: 'Person' object has no attribute '__age'
>>> worker.get_name() #通过自定义函数获取内部变量
张三

私有变量其实也并不是不能从外部访问,上面直接访问报错的原因是Python解释器把name变量变成了_Person__name,所以仍然可以通过此变量来访问,但是强烈建议不要这么使用。

1
2
3
4
5
>>> worker.__name = '李四'
>>> worker.__name
'李四'
>>> worker.get_name()
'张三'

上面代码问题在于Python编译器已经把__name属性变为_Person__name了,而外部直接给__name属性赋值相当于一个新变量,所以通过get_name()方法获取值还是以前的值。想要改变__name属性值可以自定义set_name()函数。

在绑定属性时,如果直接把属性暴露出去,就无法检查属性的值,所以通常定义get和set方法进行读取操作,但是定义get和set方法又有些麻烦,可以通过@property装饰器来简化这一问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person(object) :
@property #此装饰器可以实现[实例.sex]进行读取操作
def sex(self) :
return self._sex

@sex.setter #此装饰器可以实现[实例.sex=value]进行赋值操作
def sex(self,value) :
if value > 2 :
raise ValueError('无效赋值')
self._sex = value

>>> p = Person()
>>> p.sex = 2
>>> print(p.sex)
2
>>> p.sex = 3
ValueError: 无效赋值

把一个get方法变成属性调用只需要在方法上加上@property装饰器,然后就可以直接用.操作符调用属性了,@property装饰器会自动创建一个@属性名.setter装饰器,负责把set方法变成属性赋值。

继承

继承可以拥有父类的全部功能,语法就是在类名后面加上(继承类)。Python也支持多重继承,只需逗号分割即可。

1
2
3
4
5
6
class Worker(Person) :
pass

>>> worker = Worker('张三',30)
>>> worker.get_name() #空类Worker因为继承Person也拥有了父类函数
'张三'

在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类,反过来就不行。

1
2
3
4
5
6
7
>>> isinstance(worker,Worker)
True
>>> isinstance(worker,Person)
True
>>> person = Person('张三',30)
>>> isinstance(person,Worker)
False

继承还可以一级一级地继承下来,而任何类,最终都可以追溯到根类object。子类还可以重写父类函数。

限制定义slots

创建一个类的实例后,可以动态给实例绑定方法和属性,这是动态语言的灵活性,在静态语言中很难实现。但每个实例绑定元素其它实例无法使用,想要在所有实例中使用,可以在类中绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#实例绑定方法
def say_hi() :
print('hi,Worker')

worker = Worker('张三',30)
worker2 = Worker('李四',25)
worker.say_hi = say_hi
worker.say_hi() --> 'hi,Worker'
worker2.say_hi() --> AttributeError: 'Worker' object has no attribute 'say_hi'

#类绑定函数
def say_hi(self) : #注意多了个self参数,调用时不用传递
print('hi,Worker')
Worker.say_hi = say_hi
worker.say_hi() --> 'hi,Worker'
worker2.say_hi() --> 'hi,Worker'

如果想要限制实例可以定义的属性,使用__slots__变量用tuple类型绑定允许定义的变量:

1
__slots__ = ('name','age')

定制类

Python中有很多类似__xxx__这种形式的变量或函数是有特殊用途的,它们可以帮助定制类。

  • __str__()

定义一个类使用print()打印这个类实例:

1
2
>>> print(Person('张三',30))
<__main__.Person object at 0x109afb190>

上面打印实例返回<main.Person object at 0x109afb190>,通过__str__()可以自定义出返回信息。

1
2
3
4
5
6
7
class Person(object) :
def __str__(self) :
return 'Person object'


>>> print(Person()) #打印实例返回自定义信息
'Person object'
  • __repr__()

__str__()函数是调用print()起作用,而如果直接调用实例不使用print()函数时此方法不会起作用,此时需要使用__repr__()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> p = Person()
>>> p #直接调用实例__str__()方法无效
<__main__.Person object at 0x109afb190>

|改|
|进|
|代|
|码|
V V

class Person(object) :
...省略

__repr__ = __str__

>>> p
'Person object'

通常__str__()__repr__()函数代码都一样,所以可以直接把__str__赋值给__repr__

  • __iter__()

此函数可以使类实例作用于for…in循环,循环会通过__next__()方法拿到下一个值,直到遇到StopIteration时退出循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Fib(object) :
def __init__(self,range) :
self.a,self.b = 0,1
self.range = range
def __iter__(self) :
return self

def __next__(self) :
self.a,self.b = self.b,self.a+self.b

if self.a > self.range :
raise StopIteration()
return self.a

>>> [x for x in Fib(10)]
[1, 1, 2, 3, 5, 8]
  • __getitem__()

此函数可以使类实例像列表(list)一样通过索引获取元素。

1
2
3
4
5
6
7
8
9
class FibList(object) :
def __getitem__(self,n) :
a,b = 0,1
for x in range(n) :
a,b = b,a+b
return a

>>> FibList()[7]
13

__getitem__()函数也可以根据isinstance()判断类型实现切片(slice)、字典(dict)等操作。类似的函数还有__setitem__()__delitem__()

  • __getattr__()

正常情况,调用类中不存在的属性或方法会报错,如果用此函数预先定义可以避免此问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person(object) :
def __init__(self) :
self.name = 'Windus'

>>> Person().age
AttributeError: 'Person' object has no attribute 'age'

|改|
|进|
|代|
|码|
V V

class Person(object) :
def __init__(self) :
self.name = 'Windus'

def __getattr__(self,attr) :
if attr == 'age' :
return 30

>>> Person().age
30

此函数仅在属性或函数不存在时才会被调用,默认返回值是None,如果想要类只响应特殊属性或函数可以抛出AttributeError异常。

1
2
3
4
5
6
7
8
class Person(object) :

...省略

def __getattr__(self,attr) :
if attr == 'age' :
return 30
raise AttributeError('Person object has no attribute:%s' % attr)
  • __call__()

一般而言一个实例可以调用其属性和方法而实例本身无法调用,Python中可以通过此方法直接调用实例。

1
2
3
4
5
6
7
8
9
class Person(object) :
...省略

def __call__() :
print('Person instance')

>>>p = Person()
>>>p()
Person instance

**判断一个变量是函数还是对象,可以判断该对象能否被调用,能被调用就是一个callable对象,使用callable()

枚举

  • 定义枚举
1
2
3
from enum import Enum

Month = Enum('Months', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
  • 遍历枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)

Jan => Months.Jan , 1
Feb => Months.Feb , 2
Mar => Months.Mar , 3
Apr => Months.Apr , 4
May => Months.May , 5
Jun => Months.Jun , 6
Jul => Months.Jul , 7
Aug => Months.Aug , 8
Sep => Months.Sep , 9
Oct => Months.Oct , 10
Nov => Months.Nov , 11
Dec => Months.Dec , 12
  • 自定义枚举类
1
2
3
4
5
6
7
8
9
10
11
from enum import Enum

@unique #装饰器保证没有重复值
class Color(Enum) :
red = 1 # red的value被设定为1
orange = 2
yellow = 3
green = 4
blue = 5
indigo = 6
purple = 7

元类

动态语言函数和类的定义不是在编译时定义,而是在运行时创建的。而创建class的方法就是type()函数。

通过type定义一个简单的类,type接收三个参数(类名,父类元组,类函数和属性字典字典)

1
2
3
4
5
6
7
8
def fn(self) :
print('Hello Person')

Person = type('Person',(object,),dict(say_hello=fn))

>>> p = Person()
>>> say_hello()
Hello Person

除了type()函数外,还可以通过metaclass来创建类,metaclass被称为元类,一般元类类名都以Metaclass结尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# metaclass是类的模板,所以必须从`type`类型派生
class ListMetaclass(type) :
#传入参数(cls:当前实例化的类(必传)name:类名bases:基类列表attrs:属性和函数字典
def __new__(cls,name,bases,attrs) :
#定义add方法
attrs['add'] = lambda self,value : self.append(value)
return type.__new__(cls,name,bases,attrs)

class MyList(list,metaclass = ListMetaclass) :
pass

>>> L = MyList()
>>> L.add(10)
>>> L.add(11)
>>> print(L)
[10, 11]

定义MyList类后自动就有了add()方法,而普通list没有add()方法。其实add()方法完全可以在MyList类里直接写,没必要通过元类动态修改,然而有些情况下非常有用,如下案例。

案例一:Django框架对象-关系映射简析

要编写一个ORM框架所有类都只能动态定义,因为只有使用者才能够根据表结构定义出对应的类。

1、创建一个Field类,保存数据库表字段和字段类型

1
2
3
4
5
6
7
class Field(object) :
def __init__(self,name,column_type) :
self.name = name
self.column_type = column_type

def __str__(self) :
return '<%s:%s>' % (self.__class__.__name__,self.name)

作用:在Field类实例化时将得到两个参数,name和column_type,它们将被绑定为Field的私有属性,如果要将Field转化为字符串时,将返回“Field:XXX” , XXX是传入的name名称。

2、在Field基础上,进一步抽象各类型Field

1
2
3
4
5
6
7
8
class StringField(Field) :
def __init__(self,name) :
super(StringField,self).__init__(name,'varchar(100)')


class IntegerField(Field) :
def __init__(self,name) :
super(IntegerField,self).__init__(name,'bigint')

作用:在StringField,IntegerField实例初始化时,自动调用父类的初始化方式。

3、编写Model元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
#排除对Model类的修改
if name == 'Model':
return type.__new__(cls, name, bases, attrs)
print('found model :%s' % name)

#声明属性和列的映射关系字典
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mappings %s===>%s' % (k, v))
#把属性和列的映射关系放入mapping
mappings[k] = v
for k in mappings.keys():
#从类属性中删除Field属性
attrs.pop(k)
# 保存属性和列的映射关系到类属性__mappings__中
attrs['__mappings__'] = mappings
# 假设表名与类名一致保存表名到类属性__table__中
attrs['__table__'] = name
return type.__new__(cls, name, bases, attrs)

4、编写基类Model类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Model(dict,metaclass=ModelMetaclass) :
def __init__(self,**kw) :
super(Model,self).__init__(**kw)

def __getattr__(self,key) :
try:
return self[key]
except KeyError :
raise AttributeError("'Model' object has no attribute '%s'" % key)

def __setattr__(self,key,value) :
self[key] = value

def save(self) :
fileds = []
args = []
for k,v in self.__mappings__.items() :
fileds.append(v.name)
args.append(getattr(self,k,None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fileds), ','.join([str(i) for i in args]))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))

5、编写用户实现类User

1
2
3
4
5
6
class User(Model):
# 定义类的属性到列的映射,自动解析为Model.__setattr__(self, ‘id’, IntegerField(‘id’)),其它同理。
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')

6、调用实现类并返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#调用Model.__setattr__,将键值载入私有对象
>>> u = User(id=12345, name='Batman', email='batman@nasa.org', password='iamback') 
>>> u.save()

found model :User
Found mappings id===><IntegerField:id>
Found mappings name===><StringField:username>
Found mappings email===><StringField:email>
Found mappings password===><StringField:password>
SQL: insert into User (id) values (12345)
ARGS: [12345]
SQL: insert into User (id,username) values (12345,Batman)
ARGS: [12345, 'Batman']
SQL: insert into User (id,username,email) values (12345,Batman,batman@nasa.org)
ARGS: [12345, 'Batman', 'batman@nasa.org']
SQL: insert into User (id,username,email,password) values (12345,Batman,batman@nasa.org,iamback)
ARGS: [12345, 'Batman', 'batman@nasa.org', 'iamback']