Python全面采用了面向对象的思想,是真正的面向对象的编程语言.一切皆为对象
区别
面向过程思维
面向对象思维
"设计者"思维,适合编写大型程序
更加关注"对象之间的关系"
我们首先思考"怎么设计这个东西"
然后我们把这个东西拆分成一个个的物体object(对象)
然后通过分工协作来完成,每个类完成一个部分
例如造车\飞机\轮船\火箭,我们必须先要确定这些复杂的设备是由什么构成的。
汽车
发动机 ->发动机厂生产
轮胎 ->轮胎厂生产
座椅 -> 座椅厂生产
挡风玻璃 ->玻璃厂生产
相同点
配合
一切皆对象
对象是数据和操作的封装
对象是独立的,但是对象之间可以相互作用
目前面向对象是最接近人类认知的编程范式。
1.封装
2.继承
3.多态
鸭子类型实现多态,一个动物,走起路来像鸭子,叫声也想鸭子,那么我们就让我它是鸭子。
面向对象编程最灵活的地方,动态绑定
子类继承父类后,同样的方法实现不同的功能。
json dump()load() dumps() loads()
pickle dump() load() dumps() loads()
1.类的概念
class 类名:定义属性或方法
class 类名:
语句块
1.必须使用class关键字
2.类名必须是使用大写驼峰命名
3.类名:满足这类事物的名字
4.属性: 这个类创建出来的对象有什么样的特征
5.方法:这个类创建出来的对象有什么样的行为
6.类定义完成后,就产生了一个类对象,绑定到了ClassName上了。
class MyClass:
"""
这是一个类
"""
x = "abc" # 类属性
def foo(self): # 类属性foo,也是方法
return "My Class"
print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)
class Student(object):
# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
# PEP 8要求标识符的名字用全小写多个单词用下划线连接
# 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识)
def watch_movie(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看岛国XXX电影.' % self.name)
def main():
# 创建学生对象并指定姓名和年龄
stu1 = Student('小明', 20)
# 给对象发study消息
stu1.study('Python程序设计')
# 给对象发watch_av消息
stu1.watch_movie()
stu2 = Student('王大锤', 15)
stu2.study('思想品德')
stu2.watch_movie()
if __name__ == '__main__':
main()
元类:创建类的类
类:创建对象的对象
1.id(身份识别码)
2.type(对象的类型)
3.value(对象的值)
(1)属性 attribute
(2)方法 method
我们要通过类创建好实例对象后,才能使用类定义的实例属性和方法.
函数和类也是对象,属于python的一等公民
1.可以赋值给一个变量
2.可以添加到列表,元组,集合,字典对象中
3.可以作为参数传递给函数
4.可以作为函数的返回值
在类中定义的方法必须有个额外的第一个参数,就是self,表示创建的类实例本身
属性就是在类中定义的变量
实例属性
从属于对象的属性也称实例变量
一般在__init__()
中定义
self.实例属性=初始化
调用
创建实例对象后
obj = 类名()
obj.实例属性=值
类内部 self.实例属性名
私有属性
__
开头的属性是私有属性(private),其他为公共属性(public).类属性
obj.__dict__
对象的属性字典obj.__class__
对象的所属的类class.__bases__
类的基类元组(多继承)class.__base__
类的基类class.__mro__
类的层次结构class.__subclasssess__()
子类列表方法
__new__()
用于创建对象,一般不需要我们自定义__init__()
对象的初始化,对象建立后,自动执行,初始化当前对象的相关属性,无返回值__del__()
当类被销毁时,执行的操作。__call__()
表示对象可以和函数一样被调用.__str__()
,__repr__()
# __new__() 方法是在类准备将自身实例化时调用;
def __new__(cls,*args,**kwargs):
return object.__new__(cls)
# 对象建立后,自动执行,初始化当前对象的相关属性,无返回值
def __init__(self,参数列表):
# 需要被初始化的属性或方法
pass
# 析构函数
def __del__(self):
pass
# 当类被销毁时,执行的操作
# 一般用来释放对象占用的资源
# 如打开的文件或链接的网络
# 一般不用自定义析构函数
python实现自动的垃圾回收机制,当对象没有被引用时(引用数为0),由垃圾回收器调用__del__
方法
del 销毁对象时,会调用__del__
方法
__call__()
表示对象可以和函数一样被调用.
调用函数本质是调用了__call__
方法
在Python中,函数其实是一个对象,这个对象的类中定义了__call__()
案例
__str__()
,__repr__()
用于描述对象的信息,也就是print(对象)的结果
如果你想… | 所以,你写… | Python调用… |
---|---|---|
初始化一个实例 | x = MyClass() |
x.__init__() |
作为一个字符串的"官方"表示 | repr(x) |
x.__repr__() |
作为一个字符串 | str(x) |
x.__str__() |
作为字节数组 | bytes(x) |
x.__bytes__() |
作为格式化字符串 | format(x, format_spec) |
x.__format__(format_spec) |
__init__()
方法在创建实例后调用.如果你想控制创建过程,请使用__new__()
方法__repr__()
应该返回一个有效的Python表达式的字符串__str__()
方法也被称为你的print(x)
如果你想… | 所以,你写… | Python调用… |
---|---|---|
遍历一个序列 | iter(seq) |
seq.__iter__() |
从迭代器中获取下一个值 | next(seq) |
seq.__next__() |
以相反的顺序创建一个迭代器 | reversed(seq) |
seq.__reversed__() |
__iter__()
无论何时创建新的迭代器,都会调用该方法.__next__()
每当你从迭代器中检索一下个值的时候,都会调用该方法__reversed__()
方法并不常见.它需要一个现有序列并返回一个迭代器,该序列是倒序的顺序.如果你想… | 所以,你写… | Python调用… |
---|---|---|
得到一个属性 | x.my_property |
x.__getattribute__('my_property') |
获得一个属性 | x.my_property |
x.__getattr__('my_property') |
设置一个属性 | x.my_property = value |
x.__setattr__('my_property', value) |
删除一个属性 | del x.my_property |
x.__delattr__('my_property') |
列出所有属性和方法 | dir(x) |
x.__dir__() |
__getattribute__()
方法,Python将在每次引用任何属性或方法名时调用它.__getattr__()
方法,Python只会在所有普通地方查找属性后调用它.如果一个实例x
定义了一个属性 color
, x.color
将不会调用x.__getattr__('color')
; 它将简单地返回已经定义的x.color
值.__setattr__()
只要你为属性指定值,就会调用该方法.__delattr__()
只要删除属性,就会调用该方法.__dir__()
如果您定义一个__getattr__()
或者 __getattribute__()
方法,该方法很有用.通常情况下,调用dir(x)
只会列出常规属性和方法.getattr()和__getattribute__()方法之间的区别很微妙但很重要.
通过定义__call__()方法,您可以创建一个可调用类的实例 - 就像函数可调用一样.
如果你想… | 所以,你写… | Python调用… |
---|---|---|
来"调用"像函数一样的实例 | my_instance() |
my_instance.__call__() |
如果你的类作为一组值的容器 - 也就是说,如果问你的类是否"包含"一个值是有意义的 - 那么它应该定义下面的特殊方法,使它像一个集合一样.
如果你想… | 所以,你写… | Python调用… |
---|---|---|
序列的数量 | len(s) |
s.__len__() |
否包含特定的值 | x in s |
s.__contains__(x) |
如果你想… | 所以,你写… | Python调用… |
---|---|---|
通过它的key来获得值 | x[key] |
x.__getitem__(key) |
通过它的key来设置一个值 | x[key] = value |
x.__setitem__(key, value) |
删除键值对 | del x[key] |
x.__delitem__(key) |
为丢失的key提供默认值 | x[nonexistent_key] |
x.__missing__(nonexistent_key) |
如果你想… | 所以,你写… | Python调用… |
---|---|---|
加 | x + y |
x.__add__(y) |
减 | x - y |
x.__sub__(y) |
乘 | x * y |
x.__mul__(y) |
整除 | x / y |
x.__trueiv__(y) |
除 | x // y |
x.__floordiv__(v) |
取余 | x % y |
x.__mod__(y) |
整除与取余 | divmod(x, y) |
x.__divmod__(y) |
平方 | x ** y |
x.__pow__(y) |
左移 | x << y |
x.__lshift__(y) |
右移 | x >> y |
x.__rshift__(y) |
按位and运算 | x & y |
x.__and__(y) |
按位xor或运算 | x ^ y |
x.__xor__(y) |
按位or运算 | x | y |
x.__or__(y) |
上述一组特殊方法采用第一种方法:给定x / y
,它们提供了一种方法让x
说"我知道如何用y
整除自己".以下一组特殊方法解决了第二种方法:它们为y提供了一种方法来说"我知道如何成为分母,并将自己整除x".
如果你想… | 所以,你写… | Python调用… |
---|---|---|
加 | x + y |
x.__radd__(y) |
减 | x - y |
x.__rsub__(y) |
乘 | x * y |
x.__rmul__(y) |
整除 | x / y |
x.__rtrueiv__(y) |
除 | x // y |
x.__rfloordiv__(v) |
取余 | x % y |
x.__rmod__(y) |
整除与取余 | divmod(x, y) |
x.__rdivmod__(y) |
平方 | x ** y |
x.__rpow__(y) |
左移 | x << y |
x.__rlshift__(y) |
右移 | x >> y |
x.__rrshift__(y) |
按位and运算 | x & y |
x.__rand__(y) |
按位xor或运算 | x ^ y |
x.__rxor__(y) |
按位or运算 | x | y |
x.__ror__(y) |
可是等等!还有更多!如果你正在进行"就地"操作,如x /= 3
则可以定义更多特殊的方法.
如果你想… | 所以,你写… | Python调用… |
---|---|---|
加 | x + y |
x.__iadd__(y) |
减 | x - y |
x.__isub__(y) |
乘 | x * y |
x.__imul__(y) |
整除 | x / y |
x.__itrueiv__(y) |
除 | x // y |
x.__ifloordiv__(v) |
取余 | x % y |
x.__imod__(y) |
整除与取余 | divmod(x, y) |
x.__idivmod__(y) |
平方 | x ** y |
x.__ipow__(y) |
左移 | x << y |
x.__ilshift__(y) |
右移 | x >> y |
x.__irshift__(y) |
按位and运算 | x & y |
x.__iand__(y) |
按位xor或运算 | x ^ y |
x.__ixor__(y) |
按位or运算 | x | y |
x.__ior__(y) |
还有一些"单个数"数学运算可以让你自己对类似数字的对象进行数学运算.
如果你想… | 所以,你写… | Python调用… |
---|---|---|
负数 | -x |
x.__neg__() |
正数 | +x |
x.__pos__() |
绝对值 | abs(x) |
x.__abs__() |
逆 | ~x |
x.__invert__() |
复数 | complex(x) |
x.__complex__() |
整数 | int(x) |
x.__int__() |
浮点数 | float(x) |
x.__float__() |
四舍五入到最近的整数 | round(x) |
x.__round__() |
四舍五入到最近的n位数 | round(x, n) |
x.__round__(n) |
最小整数 | math.ceil(x) |
x.__ceil__() |
最大整数 | math.floor(x) |
x.__floor__() |
截断x到0的最接近的整数 | math.trunc(x) |
x.__trunc__() |
数字作为列表索引 | a_list[x] |
a_list[x.__index__()] |
如果你想… | 所以,你写… | Python调用… |
---|---|---|
等于 | x == y |
x.__eq__(y) |
不等于 | x != y |
x.__ne__(y) |
小于 | x < y |
x.__lt__(y) |
小于等于 | x <= y |
x.__le__(y) |
大于 | x > y |
x.__gt__(y) |
大于等于 | x >= y |
x.__ge__(y) |
布尔 | if x: |
x.__bool__() |
如果你想… | 所以,你写… | Python调用… |
---|---|---|
对象副本 | copy.copy(x) |
x.__copy__() |
深拷贝 | copy.deepcopy(x) |
x.__deepcopy__() |
序列化一个对象 | pickle.dump(x, file) |
x.__getstate__() |
序列化一个对象 | pickle.dump(x, file) |
x.__reduce__() |
序列化一个对象 | pickle.dump(x, file, protocol_version) |
x.__reduce_ex__(protocol_version) |
取出恢复后的状态 | x = pickle.load(fp) |
x.__getnewargs__() |
取出恢复后的状态 | x = pickle.load(fp) |
x.__setstate__() |
with块限定了运行时上下文;在执行with语句时,"进入"上下文,并在执行块中的最后一个语句后"退出"上下文.
如果你想… | 所以,你写… | Python调用… |
---|---|---|
进入with语句块 | with x: |
x.__enter__() |
退出with语句块 | with x: |
x.__exit__(exc_type, exc_value, traceback) |
如果你想… | 所以,你写… | Python调用… |
---|---|---|
一个类的构造函数 | x = MyClass() |
x.__new__() |
一个类的析构函数 | del x |
x.__del__() |
只有一组特定的属性需要定义 | `` | x.__slots__() |
hash码 | hash(x) |
x.__hash__() |
获得一个属性的值 | x.color |
type(x).__dict__['color'].__get__(x, type(x)) |
设置一个属性的值 | x.color = 'PapayaWhip' |
type(x).__dict__['color'].__set__(x, 'PapayaWhip') |
删除一个属性 | del x.color |
type(x).__dict__['color'].__del__(x) |
一个对象是否是你的一个类的实例 | isinstance(x, MyClass) |
MyClass.__instancecheck__(x) |
一个类是否是你的类的子类 | isinstance(C, MyClass) |
MyClass.__subclasscheck__(C) |
一个类是否是抽象基类的实例 | isinstance(C, MyABC) |
MyABC.__subclasshook__(C) |
都是用用来完成一个功能语句块,本质一样
方法通过对象来调用.普通函数不需要
方法定义第一个参数是self,函数不需要
a= 类()
a.方法()
等价
类.方法(a)
私有方法
双下划线开头的方法
只能在类的内部被调用的方法
之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
# 访问器 - getter方法
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
__slots__
魔法我们讲到这里,不知道大家是否已经意识到,Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__
变量来进行限定。需要注意的是__slots__
的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 22)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')
if __name__ == '__main__':
main()
和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示。
from time import time, localtime, sleep
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
# 通过类方法创建对象并获取系统时间
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。
[外链图片转存失败(img-g2pbxlLr-1565166677221)(https://github.com/jackfrued/Python-100-Days/raw/master/Day01-15/res/uml-example.png)]
利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。
可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。
提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。
子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为。
在子类的函数中,若是要重用父类中某个函数的功能,可以直接通过 super 来调用父类中的函数,当然也可以通过 类名.func() 来调用,只不过这样与调用普通函数无异。这个经常使用在需要对父类的同名方法进行扩展的场景~
class Father:
def say(self):
print('Hello !')
def introduce(self):
print('Father')
class Son(Father):
def say(self):
super().say()
# Father.say(self) # 通过 类名.func(),输出结果一致
print('你好 ~')
p = Son()
p.say()
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。
class Person(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def watch_av(self):
if self._age >= 18:
print('%s正在观看爱情动作片.' % self._name)
else:
print('%s只能观看《熊出没》.' % self._name)
class Student(Person):
"""学生"""
def __init__(self, name, age, grade):
super().__init__(name, age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在学习%s.' % (self._grade, self._name, course))
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
super().__init__(name, age)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title(self, title):
self._title = title
def teach(self, course):
print('%s%s正在讲%s.' % (self._name, self._title, course))
def main():
stu = Student('王大锤', 15, '初三')
stu.study('数学')
stu.watch_av()
t = Teacher('小明', 38, '砖家')
t.teach('Python程序设计')
t.watch_av()
if __name__ == '__main__':
main()
通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
"""宠物"""
def __init__(self, nickname):
self._nickname = nickname
# 抽象方法,必须要被重写
@abstractmethod
def make_voice(self):
"""发出声音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
"""猫"""
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
在上面的代码中,我们将Pet
类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc
模块的ABCMeta
元类和abstractmethod
包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,Dog
和Cat
两个子类分别对Pet
类中的make_voice
抽象方法进行了重写并给出了不同的实现版本,当我们在main
函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。
from abc import ABCMeta, abstractmethod
from random import randint, randrange
class Fighter(object, metaclass=ABCMeta):
"""战斗者"""
# 通过__slots__魔法限定对象可以绑定的成员变量
__slots__ = ('_name', '_hp')
def __init__(self, name, hp):
"""初始化方法
:param name: 名字
:param hp: 生命值
"""
self._name = name
self._hp = hp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
@property
def alive(self):
return self._hp > 0
@abstractmethod
def attack(self, other):
"""攻击
:param other: 被攻击的对象
"""
pass
class Ultraman(Fighter):
"""奥特曼"""
__slots__ = ('_name', '_hp', '_mp')
def __init__(self, name, hp, mp):
"""初始化方法
:param name: 名字
:param hp: 生命值
:param mp: 魔法值
"""
super().__init__(name, hp)
self._mp = mp
def attack(self, other):
other.hp -= randint(15, 25)
def huge_attack(self, other):
"""究极必杀技(打掉对方至少50点或四分之三的血)
:param other: 被攻击的对象
:return: 使用成功返回True否则返回False
"""
if self._mp >= 50:
self._mp -= 50
injury = other.hp * 3 // 4
injury = injury if injury >= 50 else 50
other.hp -= injury
return True
else:
self.attack(other)
return False
def magic_attack(self, others):
"""魔法攻击
:param others: 被攻击的群体
:return: 使用魔法成功返回True否则返回False
"""
if self._mp >= 20:
self._mp -= 20
for temp in others:
if temp.alive:
temp.hp -= randint(10, 15)
return True
else:
return False
def resume(self):
"""恢复魔法值"""
incr_point = randint(1, 10)
self._mp += incr_point
return incr_point
def __str__(self):
return '~~~%s奥特曼~~~\n' % self._name + \
'生命值: %d\n' % self._hp + \
'魔法值: %d\n' % self._mp
class Monster(Fighter):
"""小怪兽"""
__slots__ = ('_name', '_hp')
def attack(self, other):
other.hp -= randint(10, 20)
def __str__(self):
return '~~~%s小怪兽~~~\n' % self._name + \
'生命值: %d\n' % self._hp
def is_any_alive(monsters):
"""判断有没有小怪兽是活着的"""
for monster in monsters:
if monster.alive > 0:
return True
return False
def select_alive_one(monsters):
"""选中一只活着的小怪兽"""
monsters_len = len(monsters)
while True:
index = randrange(monsters_len)
monster = monsters[index]
if monster.alive > 0:
return monster
def display_info(ultraman, monsters):
"""显示奥特曼和小怪兽的信息"""
print(ultraman)
for monster in monsters:
print(monster, end='')
def main():
u = Ultraman('迪迦', 1000, 120)
m1 = Monster('炎魔战士', 250)
m2 = Monster('哥布纽', 500)
m3 = Monster('迪莫杰厄', 750)
ms = [m1, m2, m3]
fight_round = 1
while u.alive and is_any_alive(ms):
print('========第%02d回合========' % fight_round)
m = select_alive_one(ms) # 选中一只小怪兽
skill = randint(1, 10) # 通过随机数选择使用哪种技能
if skill <= 6: # 60%的概率使用普通攻击
print('%s使用普通攻击打了%s.' % (u.name, m.name))
u.attack(m)
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
elif skill <= 9: # 30%的概率使用魔法攻击(可能因魔法值不足而失败)
if u.magic_attack(ms):
print('%s使用了魔法攻击.' % u.name)
else:
print('%s使用魔法失败.' % u.name)
else: # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)
if u.huge_attack(m):
print('%s使用究极必杀技虐了%s.' % (u.name, m.name))
else:
print('%s使用普通攻击打了%s.' % (u.name, m.name))
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
if m.alive > 0: # 如果选中的小怪兽没有死就回击奥特曼
print('%s回击了%s.' % (m.name, u.name))
m.attack(u)
display_info(u, ms) # 每个回合结束后显示奥特曼和小怪兽的信息
fight_round += 1
print('\n========战斗结束!========\n')
if u.alive > 0:
print('%s奥特曼胜利!' % u.name)
else:
print('小怪兽胜利!')
if __name__ == '__main__':
main()
import random
class Card(object):
"""一张牌"""
def __init__(self, suite, face):
self._suite = suite
self._face = face
@property
def face(self):
return self._face
@property
def suite(self):
return self._suite
def __str__(self):
if self._face == 1:
face_str = 'A'
elif self._face == 11:
face_str = 'J'
elif self._face == 12:
face_str = 'Q'
elif self._face == 13:
face_str = 'K'
else:
face_str = str(self._face)
return '%s%s' % (self._suite, face_str)
def __repr__(self):
return self.__str__()
class Poker(object):
"""一副牌"""
def __init__(self):
self._cards = [Card(suite, face)
for suite in '♠♥♣♦'
for face in range(1, 14)]
self._current = 0
@property
def cards(self):
return self._cards
def shuffle(self):
"""洗牌(随机乱序)"""
self._current = 0
random.shuffle(self._cards)
@property
def next(self):
"""发牌"""
card = self._cards[self._current]
self._current += 1
return card
@property
def has_next(self):
"""还有没有牌"""
return self._current < len(self._cards)
class Player(object):
"""玩家"""
def __init__(self, name):
self._name = name
self._cards_on_hand = []
@property
def name(self):
return self._name
@property
def cards_on_hand(self):
return self._cards_on_hand
def get(self, card):
"""摸牌"""
self._cards_on_hand.append(card)
def arrange(self, card_key):
"""玩家整理手上的牌"""
self._cards_on_hand.sort(key=card_key)
# 排序规则-先根据花色再根据点数排序
def get_key(card):
return (card.suite, card.face)
def main():
p = Poker()
p.shuffle()
players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
for _ in range(13):
for player in players:
player.get(p.next)
for player in players:
print(player.name + ':', end=' ')
player.arrange(get_key)
print(player.cards_on_hand)
if __name__ == '__main__':
main()
说明: 大家可以自己尝试在上面代码的基础上写一个简单的扑克游戏,例如21点(Black Jack),游戏的规则可以自己在网上找一找。
"""
某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统 根据提供的员工信息来计算月薪
部门经理的月薪是每月固定15000元
程序员的月薪按本月工作时间计算 每小时150元
销售员的月薪是1200元的底薪加上销售额5%的提成
"""
from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
"""
初始化方法
:param name: 姓名
"""
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
"""
获得月薪
:return: 月薪
"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
super().__init__(name)
self._working_hour = working_hour
@property
def working_hour(self):
return self._working_hour
@working_hour.setter
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self):
return 150.0 * self._working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0):
super().__init__(name)
self._sales = sales
@property
def sales(self):
return self._sales
@sales.setter
def sales(self, sales):
self._sales = sales if sales > 0 else 0
def get_salary(self):
return 1200.0 + self._sales * 0.05
def main():
emps = [
Manager('刘备'), Programmer('诸葛亮'),
Manager('曹操'), Salesman('荀彧'),
Salesman('吕布'), Programmer('张辽'),
Programmer('赵云')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
# 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
print('%s本月工资为: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()
自省:自省就是能够获得自身的结构和方法,给开发者可以灵活的调用,给定一个对象,返回该对象的所有属性和函数列表,或给定对象和该对象的函数或者属性的名字,返回对象的函数或者属性实例。
反射就是通过字字符串的形式来操作对象或者模块的成员,一种基于字符串的事件驱动。
判断对象中是否有这个方法或变量
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交谈"%self.name)
p = Person("laowang")
print(hasattr(p,"talk")) # True。因为存在talk方法
print(hasattr(p,"name")) # True。因为存在name变量
print(hasattr(p,"abc")) # False。因为不存在abc方法或变量
获取对象中的方法或变量的内存地址
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交谈"%self.name)
p = Person("laowang")
n = getattr(p,"name") # 获取name变量的内存地址
print(n) # 此时打印的是:laowang
f = getattr(p,"talk") # 获取talk方法的内存地址
f() # 调用talk方法
我们发现getattr有三个参数,那么第三个参数是做什么用的呢?
s = getattr(p,"abc","not find")
print(s) # 打印结果:not find。因为abc在对象p中找不到,本应该报错,属性找不到,但因为修改了找不到就输出not find
为对象添加变量或方法
def abc(self):
print("%s正在交谈"%self.name)
class Person(object):
def __init__(self,name):
self.name = name
p = Person("laowang")
setattr(p,"talk",abc) # 将abc函数添加到对象中p中,并命名为talk
p.talk(p) # 调用talk方法,因为这是额外添加的方法,需手动传入对象
setattr(p,"age",30) # 添加一个变量age,复制为30
print(p.age) # 打印结果:30
删除对象中的变量。注意:不能用于删除方法
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交谈"%self.name)
p = Person("laowang")
delattr(p,"name") # 删除name变量
print(p.name) # 此时将报错
元类是python高阶语法. 合理的使用可以减少大量重复性的代码.
元类实际上做了以下三方面的工作:
为什么要使用元类这种模糊且容易出错的功能?
一般情况下,我们并不会使用元类,99%的开发者并不会用到元类,所以一般不用考虑这个问题。
元类主用用于创建API,一个典型的例子就是Django的ORM。
它让我们可以这样定义一个类:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
guy = Person(name='bob', age='35')
print(guy.age)
返回的结果是int类型而不是IntegerField对象。这是因为models.Model使用了元类,它会将Python中定义的字段转换成数据库中的字段。
通过使用元类,Django将复杂的接口转换成简单的接口。
原型:type(类名,基类元组(可以为空,用于继承), 包含属性或函数的字典)
以下两种写法都可以:
type(‘Class’,(object,),dict(hello=fun()))
type(‘Class’,(object,),{“hello”:fun()})
1、class 自定义的类名称
2、(object,)是继承类,的元组,如果只有一个就写这种形势(object,);多个(object,xxxx,)
3、dict(hello=fun()) 或 {“hello”:fun()} 第三个参数,是一个字典等号左是 自定义的方法名,右侧是已写好的方法名,这个要注意,有参数且没有默认值的情况下,要加括号;
def fun():
print('hello world!')
if __name__=="__main__":
Hello = type('Hello',(object,),dict(hello=fun()))
tc = Hello()
tc.hello
引用:
h 相当于接收Hello类;tc = h()实例化类;tc.hello方法,调用的其实是我们定义的fun方法。
Hello = type('Hello',(object,),dict(f1=fun)
tc = Hello()
tc.f1
type()动态创建类后,还可以添加更多的方法和属性:
def add(x,y):
return x + y
Hello.add = add(1, 2)
调用:
print(tc.add)
元类是用来创建其他的类,它的实例就是其他的类。
执行类定义时,解释器会先寻找类属性__metaclass__
,如果此属性中未定义,则向上查找其父类中的__metaclass__
type
的__init__
函数有三个位置参数,分别为:类名称,父类名称元祖,类属性字典;可直接使用type()
快速创建类对象:
People = type('People',(object,),dict(show = fn, setage = fn1, age = None))
也可子类化type
类后,创建元类,再创建类对象:
from time import ctime
class MetaC(type):
def __init__(self, *args):
super().__init__(*args)
print('调用类的init')
def __call__(self, *args, **kwargs):
print('调用类的call')
_instance = super().__call__(*args, **kwargs)
print('call return %s' %_instance)
return _instance
class Foo(metaclass=MetaC):
# __metaclass__ = MetaC
def __init__(self, version=None):
print('调用对象的init')
def __new__(cls, *args, **kwargs):
print('调用对象的new')
_instance = super().__new__(cls)
print('new return %s' %_instance)
return _instance
foo = Foo('hello')
运行结果:
>>
调用类的init
调用类的call
调用对象的new
new return <__main__.Foo object at 0x0000018D2F57EF98>
调用对象的init
call return <__main__.Foo object at 0x0000018D2F57EF98>
下面是几种单例模式的实现方法,有些会用到上述的元类知识。
import weakref
frome functools import wraps
def single_obj(cls):
#实例缓存为弱引用,实例不被使用时会释放该实例
_spam_cache = weakref.WeakValueDictionary()
@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in _spam_cache :
_instance = cls(*args, **kwargs)
_spam_cache [cls] = _instance
return _instance
else:
return _spam_cache [cls]
return wrapper
@single_obj
class A():
def __init__(self, version):
self.version = version
print(self.version)
a1 = A(1.3)
a2 = A(1.2)
print('a1 id:',id(a1))
print('a2 id:',id(a2))
__call__
方法实现单例class Singleton(type):
def __init__(self, *args, **kwargs):
self._instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = super().__call__(*args, **kwargs)
return self._instance
else:
return self._instance
class B(metaclass=Singleton):
def __init__(self, name):
self.name = name
print(name)
b1 = B('what')
b2 = B('hell')
print(b2.name)
print('b1 id:',id(b1))
print('b2 id:',id(b2))
__new__
实现单例(此方法不可行)class C(object):
_instance = None
def __init__(self, name):
self.name = name
print(name)
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
c1 = C('dsfsfd')
c2 = C('java')
print('c1 id:',id(c1))
print('c2 id:',id(c2))
__init__
都会被调用,实例对象会被修改;1.1 Python内置异常
Python的异常处理能力是很强大的,它有很多内置异常,可向用户准确反馈出错信息。
内置异常类的层次结构如下:
BaseException # 所有异常的基类
±- SystemExit # 解释器请求退出
±- KeyboardInterrupt # 用户中断执行(通常是输入^C)
±- GeneratorExit # 生成器(generator)发生异常来通知退出
±- Exception # 常规异常的基类
±- StopIteration # 迭代器没有更多的值
±- StopAsyncIteration # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
±- ArithmeticError # 各种算术错误引发的内置异常的基类
| ±- FloatingPointError # 浮点计算错误
| ±- OverflowError # 数值运算结果太大无法表示
| ±- ZeroDivisionError # 除(或取模)零 (所有数据类型)
±- AssertionError # 当assert语句失败时引发
±- AttributeError # 属性引用或赋值失败
±- BufferError # 无法执行与缓冲区相关的操作时引发
±- EOFError # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发
±- ImportError # 导入模块/对象失败
| ±- ModuleNotFoundError # 无法找到模块或在在sys.modules中找到None
±- LookupError # 映射或序列上使用的键或索引无效时引发的异常的基类
| ±- IndexError # 序列中没有此索引(index)
| ±- KeyError # 映射中没有这个键
±- MemoryError # 内存溢出错误(对于Python 解释器不是致命的)
±- NameError # 未声明/初始化对象 (没有属性)
| ±- UnboundLocalError # 访问未初始化的本地变量
±- OSError # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类
| ±- BlockingIOError # 操作将阻塞对象(e.g. socket)设置为非阻塞操作
| ±- ChildProcessError # 在子进程上的操作失败
| ±- ConnectionError # 与连接相关的异常的基类
| | ±- BrokenPipeError # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入
| | ±- ConnectionAbortedError # 连接尝试被对等方中止
| | ±- ConnectionRefusedError # 连接尝试被对等方拒绝
| | ±- ConnectionResetError # 连接由对等方重置
| ±- FileExistsError # 创建已存在的文件或目录
| ±- FileNotFoundError # 请求不存在的文件或目录
| ±- InterruptedError # 系统调用被输入信号中断
| ±- IsADirectoryError # 在目录上请求文件操作(例如 os.remove())
| ±- NotADirectoryError # 在不是目录的事物上请求目录操作(例如 os.listdir())
| ±- PermissionError # 尝试在没有足够访问权限的情况下运行操作
| ±- ProcessLookupError # 给定进程不存在
| ±- TimeoutError # 系统函数在系统级别超时
±- ReferenceError # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
±- RuntimeError # 在检测到不属于任何其他类别的错误时触发
| ±- NotImplementedError # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现
| ±- RecursionError # 解释器检测到超出最大递归深度
±- SyntaxError # Python 语法错误
| ±- IndentationError # 缩进错误
| ±- TabError # Tab和空格混用
±- SystemError # 解释器发现内部错误
±- TypeError # 操作或函数应用于不适当类型的对象
±- ValueError # 操作或函数接收到具有正确类型但值不合适的参数
| ±- UnicodeError # 发生与Unicode相关的编码或解码错误
| ±- UnicodeDecodeError # Unicode解码错误
| ±- UnicodeEncodeError # Unicode编码错误
| ±- UnicodeTranslateError # Unicode转码错误
±- Warning # 警告的基类
±- DeprecationWarning # 有关已弃用功能的警告的基类
±- PendingDeprecationWarning # 有关不推荐使用功能的警告的基类
±- RuntimeWarning # 有关可疑的运行时行为的警告的基类
±- SyntaxWarning # 关于可疑语法警告的基类
±- UserWarning # 用户代码生成警告的基类
±- FutureWarning # 有关已弃用功能的警告的基类
±- ImportWarning # 关于模块导入时可能出错的警告的基类
±- UnicodeWarning # 与Unicode相关的警告的基类
±- BytesWarning # 与bytes和bytearray相关的警告的基类
±- ResourceWarning # 与资源使用相关的警告的基类。被默认警告过滤器忽略。
详细说明请参考:https://docs.python.org/3/library/exceptions.html#base-classes
当发生异常时,我们就需要对异常进行捕获,然后进行相应的处理。
python的异常捕获常用try…except…结构
把可能发生错误的语句放在try模块里
用except来处理异常
每一个try,都必须至少对应一个except。
此外,与python异常相关的关键字主要有:
try/except 捕获异常并处理
pass 忽略异常
as 定义异常实例(except MyError as e)
else 如果try中的语句没有引发异常,则执行else中的语句
finally 无论是否出现异常,都执行的代码
raise 抛出/引发异常
包括键盘中断和程序退出请求(用sys.exit()就无法退出程序了,因为异常被捕获了),因此慎用。
try:
<语句>
except:
print('异常说明')
try:
<语句>
except <异常名>:
print('异常说明')
万能异常:
try:
<语句>
except Exception:
print('异常说明')
一个例子:
try:
f = open("file-not-exists", "r")
except IOError as e:
print("open exception: %s: %s" %(e.errno, e.strerror))
捕获多个异常有两种方式,第一种是一个except同时处理多个异常,不区分优先级:
try:
<语句>
except (<异常名1>, <异常名2>, ...):
print('异常说明')
第二种是区分优先级的:
try:
<语句>
except <异常名1>:
print('异常说明1')
except <异常名2>:
print('异常说明2')
except <异常名3>:
print('异常说明3')
该种异常处理语法的规则是:
执行try下的语句,如果引发异常,则执行过程会跳到第一个except语句。
如果第一个except中定义的异常与引发的异常匹配,则执行该except中的语句。
如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制。
如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。
如果判断完没有某些异常之后还想做其他事,就可以使用下面这样的else语句。
try:
<语句>
except <异常名1>:
print('异常说明1')
except <异常名2>:
print('异常说明2')
else:
<语句> # try语句中没有异常则执行此段代码
try...finally...语句无论是否发生异常都将会执行最后的代码。
try:
<语句>
finally:
<语句>
看一个示例:
str1 = 'hello world'
try:
int(str1)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
except ValueError as e:
print(e)
else:
print('try内没有异常')
finally:
print('无论异常与否,都会执行我')
可以使用raise语句自己触发异常,raise语法格式如下:
raise [Exception [, args [, traceback]]]
语句中Exception是异常的类型(例如ValueError),参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是"None"。最后一个参数是跟踪异常对象,也是可选的(在实践中很少使用)。
看一个例子:
def not_zero(num):
try:
if num == 0:
raise ValueError('参数错误')
return num
except Exception as e:
print(e)
not_zero(0)
Python循环语句可以作用域任何序列类型,包括列表、元组以及字符串。
实际上for循环能够作用于任何可迭代的对象,除了for语句,python中所有会从左至右的迭代工具都是如此,这些迭代工具包括:for循环、列表解析、in成员关系测试以及map内置函数等…
这里就涉及到很重要的一个概念-----可迭代对象,除此之外还有一个与它很类似的概念,叫做迭代对象,很多人经常分不清楚他们。
迭代对象是指实现了__iter__
与__next__
方法的对象,而可迭代对象可以只实现__iter__
方法,也可以两个都实现。有的可迭代对象的迭代对象就是它本身。说了那么多,不如我们直接自己实现一下:
class MyRange(object):
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
myRange = MyRange(3)
for i in myRange:
print i
句。
如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制。
如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。
如果判断完没有某些异常之后还想做其他事,就可以使用下面这样的else语句。
try:
<语句>
except <异常名1>:
print('异常说明1')
except <异常名2>:
print('异常说明2')
else:
<语句> # try语句中没有异常则执行此段代码
try...finally...语句无论是否发生异常都将会执行最后的代码。
try:
<语句>
finally:
<语句>
看一个示例:
str1 = 'hello world'
try:
int(str1)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
except ValueError as e:
print(e)
else:
print('try内没有异常')
finally:
print('无论异常与否,都会执行我')
可以使用raise语句自己触发异常,raise语法格式如下:
raise [Exception [, args [, traceback]]]
语句中Exception是异常的类型(例如ValueError),参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是"None"。最后一个参数是跟踪异常对象,也是可选的(在实践中很少使用)。
看一个例子:
def not_zero(num):
try:
if num == 0:
raise ValueError('参数错误')
return num
except Exception as e:
print(e)
not_zero(0)
Python循环语句可以作用域任何序列类型,包括列表、元组以及字符串。
实际上for循环能够作用于任何可迭代的对象,除了for语句,python中所有会从左至右的迭代工具都是如此,这些迭代工具包括:for循环、列表解析、in成员关系测试以及map内置函数等…
这里就涉及到很重要的一个概念-----可迭代对象,除此之外还有一个与它很类似的概念,叫做迭代对象,很多人经常分不清楚他们。
迭代对象是指实现了__iter__
与__next__
方法的对象,而可迭代对象可以只实现__iter__
方法,也可以两个都实现。有的可迭代对象的迭代对象就是它本身。说了那么多,不如我们直接自己实现一下:
class MyRange(object):
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
myRange = MyRange(3)
for i in myRange:
print i