面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
- 数据封装、继承和多态是面向对象的三大特点
- 面向对象的设计思想是抽象出Class,根据Class创建Instance
- 面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
定义类及初始化变量:
class Student(object):
# 在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。
# 通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去
def __init__(self, name, score):
self.name = name
# 添加下划线可以让变量私有,禁止从外部访问他
# 如果还想修改这个变量 可以提供外部方法
self._score = score
def print_score(self):
print('个人信息:%s: %s' % (self.name, self._score))
def get_grade(self):
if self._score >= 90:
return 'A'
elif self._score >= 60:
return 'B'
else:
return 'C'
def set_score(self, score_up):
self._score = score_up
在调用的时候就要先将类实例化,然后调用相应函数:
student = Student('王八羔子', 80)
student.print_score()
# 如果给外部变量添加 下划线“_”就可以让该变量变成一个私有变量而且从外部无法访问
# 双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name
# 是因为Python解释器对外把_score变量改成了_Student__score,所以,仍然可以通过_Student__score来访问_name变量:
# 强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
# Python本身没有任何机制阻止你干坏事,一切全靠自觉
student.name = '张三'
student.print_score()
print("这个家伙的成绩标准是:" + student.get_grade())
student.set_score(50)
student.print_score()
print("这个家伙的成绩标准是:" + student.get_grade())
打印结果:
个人信息:王八羔子: 80
个人信息:张三: 80
这个家伙的成绩标准是:B
个人信息:张三: 50
这个家伙的成绩标准是:C
类的继承和多态
class Animal(object):
def run(self):
print('Animal is running...')
# 继承
class Dog(Animal):
def run(self):
print('Dog is running...')
# 继承
class Cat(Animal):
def run(self):
print('Cat is running...')
调用:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
打印结果:
Dog is running...
Cat is running...
python中的鸭子类型:
#对于静态语言(例如Java)来说,如果需要传入Animal类型,
#则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
#对于Python这样的动态语言来说,则不一定需要传入Animal类型。
#我们只需要保证传入的对象有一个run()方法就可以了:
#这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,
#一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子
#Python的“file-like object“就是一种鸭子类型。对真正的文件对象,
#它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,
#都被视为“file-like object“。许多函数接收的参数就是“file-like object“,
#你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。
比如我们定义如下函数:
def run_twice(an):
if hasattr(an, 'run'):
an.run()
return None
比如上述函数,根据函数体我们知道他接收一个示例,这个示例有一个run方法
我们在上述定义的Animal、Dog、Cat都有一个run函数,我们现在在定义一个Person类:
他同样具有所谓的run方法
class People():
def run(self):
print('People is running...')
那么如下调动都是没有问题的:
run_twice(dog)
run_twice(cat)
run_twice(people)
#判断对象是否是函数 使用types模块中定义的常量 import types
#type()函数:判断对象类型
#基本类型 函数 类都可以使用:
print(type(123))
print(type(‘编的跟真的一样’))
print(type(True))
print(type(dog))
print(type(cat))
print(type(abs))
打印结果:
<class ‘int’>
<class ‘str’>
<class ‘bool’>
<class ‘module.test_class.Dog’>
<class ‘module.test_class.Cat’>
<class ‘builtin_function_or_method’>
#判断class的类型,使用isinstance()函数,返回True或者False
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
print(isinstance(cat, Cat))
print(isinstance(cat, Animal))
print(isinstance(dog, Cat))
#判断一个变量是否是某些类型中的一种
print(isinstance((1, 2, 3), (list, tuple)))
print(isinstance([1, 2, 3], (list, tuple)))
#获得一个对象的所有属性和方法,使用dir()函数
print(dir(dog))
print(dir(‘str’))
# Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Teacher(object):
# Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
__slots__ = ('name', 'age')
# __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
def __init__(self, name, age):
if isinstance(name, str):
self.name = name
if isinstance(age, int):
self.age = age
elif isinstance(age, float):
self.age = int(age)
elif isinstance(age, str):
self.age = int(age)
else:
self.age = 18
def print_other(self):
print('教师个人信息:%s: %s' % (self.name, self.age))
------------分割线----------------
在介绍上述__slots__变量的时候我们先插播getattr()、setattr()以及hasattr()这几个函数
hasattr():对象是否有某个属性或者方法 返回True或者False
hasattr(obj, 'y') # 有属性'y'吗?
setattr():为对象设置一个属性
setattr(obj, 'y', 19) # 设置一个属性'y'
getattr() 获取对象的属性或者方法
getattr(obj, 'z') # 获取属性'z'
getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn,fn指向obj.power
if not hasattr(student, 'sex'):
student.sex = '男'
print(student.sex)
------------分割线----------------
teacher = Teacher("二狗子", 30)
teacher.print_other()
# teacher.sex = '男' # 错误 在类中通过__slots__已经限制了只能添加name age两个属性
通过Python内置的@property装饰器把一个方法变成属性调用
class Tables(object):
# 把一个getter方法变成属性,只需要加上@property就可以了,
# 此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
# 只读属性
@property
def grade(self):
if self._score >= 90:
return 'A'
elif self._score >= 60:
return 'B'
else:
return 'C'
调用:
tables = Tables()
tables.score = 80 # OK,实际转化为tables .set_score(80)
print('成绩是:' + str(tables.score) + '----所在级别:' + tables.grade) # OK,实际转化为tables.get_score()
不同于java中的单继承,python中支持多继承 ,也就是说一个类可以有多个父类或者基类
# 本示例主要用来讲解多重继承
class Animal2(object):
def cry(self):
print("我是一个会叫的小动物")
# 哺乳类
class Mammal(Animal2):
def temperature(self):
print('我是一只恒温哺乳动物')
# 鸟类
class Bird(Animal2):
def temperature(self):
print('我是一只恒温鸟类')
# 可以跑的动物
class Runnable(object):
def run(self):
print('瞎几把跑吧...')
# 可以飞的动物
class Flyable(object):
def fly(self):
print('我要飞得更高...')
# 各种动物:
class Dog2(Mammal, Runnable):
pass
class Cat2(Mammal):
pass
# 各种鸟
class Parrot(Bird,Flyable):
pass
class Ostrich(Bird):
pass
调用:
dog2 = Dog2()
cat2 = Cat2()
parrot = Parrot()
ostrich = Ostrich()
dog2.cry()
dog2.temperature()
dog2.run()
print('----------------------------------')
cat2.cry()
cat2.temperature()
print('----------------------------------')
parrot.cry()
parrot.temperature()
parrot.fly()
print('----------------------------------')
ostrich.cry()
ostrich.temperature()
打印结果:
我是一个会叫的小动物
我是一只恒温哺乳动物
瞎几把跑吧...
----------------------------------
我是一个会叫的小动物
我是一只恒温哺乳动物
----------------------------------
我是一个会叫的小动物
我是一只恒温鸟类
我要飞得更高...
----------------------------------
我是一个会叫的小动物
我是一只恒温鸟类
----------分割线-------------
__iter__
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,
该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法
拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器
def __iter__(self):
return self # 示例本身就是迭代对象 故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100:
raise StopIteration
return self.a
# 如果想要实现像list那样按照下标取出元素,需要实现__getitem__()方法:
# 传入的参数可能是个int 也可能是个slice切片
def __getitem__(self, item):
if isinstance(item, int):
a, b = 1, 1
for x in range(item):
a, b = b, a + b
return a
elif isinstance(item, slice):
start = item.start
stop = item.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
# 定制类
# 一个类作用于for循环
for n in Fib():
print(n)
# 根据下标取出元素
f = Fib()
print(f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9])
print(f[:10]) # 传入的不是整数 是个切片
print(f[:10:2]) #没有对step参数作处理:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
其实这个也没有对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。
此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。
与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。
最后,还有一个__delitem__()方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,
这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
---------------------分割线--------------------------------
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错
但是__getattr__()方法,可以动态返回一个属性
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:
s = Student()
s.name
s.score
打印结果
'Michael'
99
返回函数也可以
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
调用:
print(s.age())
只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。
这种功能可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段,可以针对完全动态的情况作调用
利用完全动态的__getattr__,我们可以写出一个链式调用:
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
调用:
ch = Chain().status.user.timeline.list
print(ch)
返回结果
'/status/user/timeline/list'
--------------------------分割线-------------------------------
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
调用:
s = Student('Michael')
print(s())#self参数不要传入
打印结果:
My name is Michael.
---------------------------分割线-----------------------------------
枚举类
from enum import Enum, unique
# 枚举类
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value) # value属性则是自动赋给成员的int常量,默认从1开始计数。
# Enum派生自定义类
@unique
class WeekDay(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
# 调用方式
print('--------------', WeekDay.Sun)
print('--------------', WeekDay['Sun'])
print('--------------', WeekDay.Sun.value)
# 实体类中如果某些属性的取值是固定的话可以使用定义枚举类赋值
--------------------------分割线-----------------------------------
# type()
# 动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
# 在静态语言中 不论是函数 类还是变量 必须先定义后使用
# 但是在python中 type()函数既可以返回一个对象的类型,又可以创建出新的类型,
# 比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:
def fn(self, name='world'): # 先定义函数
print('Hello %s' % name)
# 要创建一个class对象,type()函数依次传入3个参数:
# class的名称;
# 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
#(第二个参数是个父类的tuple集合,一个元素的tuple要在括号中加,)
# class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
Hello = type('Hello', (object,), dict(hello=fn)) # 创建hello class
h = Hello()
h.hello()
print(type(Hello))
print(type(h))
打印结果:
Hello world
<class 'type'>
<class '__main__.Hello'>
最后:
metaclass
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
metaclass,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
连接起来就是:先定义metaclass,就可以创建类,最后创建实例。
所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
# 定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:
# metaclass是类的模板,所以必须从`type`类型派生:
# 给我们自定义的MyList增加一个add方法:
class ListMetaclass(type):
# 当前准备创建的类的对象;
# 类的名字;
# 类继承的父类集合;
# 类的方法集合。
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
# 当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,
# 要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
class MyList(list, metaclass=ListMetaclass):
pass
调用:
# 使用metaclass来创建类
L = MyList()
L.add(1)
print(L)
结果:
[1]
需要通过metaclass修改类定义的。ORM就是一个典型的例子,详细了解请查看相关资料