目录
面向对象概述
编程发展至今有面向过程编程、函数式编程、面向对象编程三大流派,未来不知道会不会有面向数据的编程。
Python支持面向对象编程,为什么要面向对象呢?面向对象是个啥?我的粗浅理解,就是将一系列相干的数据、方法封装到一起形成实例,并限制实例的数据、方法调用方式,通过对这个实例的控制达到简化编程过程,提高代码可读性的目的。比如,C++怎么实现面向对象的呢,数据用struct结构封装,成员函数其实就是普通的函数但入参多了个this指针,在调用函数的时候this指针取struct实例的地址,再把这个特殊的struct加个关键字class,这样就实现了class类和类的绝大多数功能。这就是面向对象的实现方法。Java也不例外。那么Python呢?
面向对象的一种实现
Python中,属性数据可以用字典封装,函数可以作为对象被封装在字典里面,函数可以嵌套从而限制其不能在作用域外被调用。根据这个思想,我们可以定义一个狗函数dog,嵌套函数定义狗的两个方法bark、wag_tail,再用字典封装狗的属性。这样就实现了面向对象的设计。调用函数的时候,传入自己作为参数,实际上就是类似C++里面的this指针。
def dog(name, type, gender):
def bark(dog):
"""
狗会叫
:return:
"""
print('%s is wang!wang!' % dog['name'])
pass
def wag_tail(dog):
"""
狗会摇尾巴
:return:
"""
print('%s wag tail %s' % (dog['name'], dog['type']))
pass
def init():
"""
初始化狗,名字,品种,公母
会叫,会摇尾巴
:return:
"""
dog_property = {
'name': name,
'type': type,
'gender': gender,
'bark': bark, # 将会叫函数作为属性
'wag_tail': wag_tail, # 将会摇尾巴函数作为属性
}
return dog_property
pass
ret = init()
return ret
pass
d1 = dog('Snoopy', '哈士奇', 'female')
print(d1)
d1['bark'](d1)
d1['wag_tail'](d1)
# {'name': 'Snoopy', 'type': '哈士奇', 'gender': 'femal', 'bark': <function dog.<locals>.bark at 0x00000000028A50D0>, 'wag_tail': <function dog.<locals>.wag_tail at 0x0000000003BAA620>}
# Snoopy is wang!wang!
# Snoopy wag tail 哈士奇
d2 = dog('旺财', '柴犬', 'male')
print(d2)
d2['bark'](d2)
d2['wag_tail'](d2)
# {'name': '旺财', 'type': '柴犬', 'gender': 'mail', 'bark': <function dog.<locals>.bark at 0x0000000003BAA6A8>, 'wag_tail': <function dog.<locals>.wag_tail at 0x0000000003BAA730>}
# 旺财 is wang!wang!
# 旺财 wag tail 柴犬
类的相关知识
类描述一类事物共有的数据(属性)和动作(方法)。类本身是没有具体信息的,是一个抽象的类型。类可以具体化到对象。
class Dog:
"""
这是一个狗类
"""
fur = 'property fur'
def bark():
print('from bark')
pass
def wag_tail(self):
print('from wag_tail')
pass
pass
print(Dog)
# <class '__main__.Dog'>
# 调用类的方法
Dog.bark()
# from bark
Dog.wag_tail('123')
# from wag_tail
# 比较dir函数的输出和类__dict__的输出
# 特别是自定义的fur、bark、wag_tail
# dir只列出属性的名字
# __dict__累出属性的名字和值
print(dir(Dog))
# ['__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__', 'bark', 'fur', 'wag_tail']
print(Dog.__dict__)
# {'__module__': '__main__',
# '__doc__': '\n 这是一个狗类\n ',
# 'fur': 'property fur',
# 'bark': <function Dog.bark at 0x0000000003BBA620>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BBA6A8>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>}
# Dog类里面有fur属性,可以用类名直接调用
print(Dog.fur)
# Dog的__dict__属性,查看类的属性字典
# 上面直接调用Dog.fur实际上也是用属性字典实现的
print(Dog.__dict__['fur'])
print(Dog.__name__) # 类的名字Dog
print(Dog.__doc__) # 类的说明文档,也就是类定义后紧跟的说明"""这是一个狗类""""
print(Dog.__bases__) # Python支持多继承,后面仔细研究,__bases__以元组形式列出了所以父类(<class 'object'>,)
对象的相关知识
我们在面向对象的一种实现那一节用函数实现了面向对象的设计。Python内置了面向对象编程的设计。
我们自己嵌套定义的初始化函数init,Python类里面有内置的__init__方法来初始化,__init__必须有self入参,自动将实例本身传给self。自动进行初始化和返回实例,因此__init__不用return。类创建实例的过程和函数调用类似。
对象只保存数据属性,不保存方法属性。这样,每个对象存自己的数据,共享一份方法。
对象调用方法的时候,将对象本身传给self参数。通过self来访问对象各自的数据。
class Dog:
"""
这是一个狗类
"""
fur = 'property fur'
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品种
self.gender = gender
pass
def bark(self):
print('%s 汪汪!' % self.name)
pass
def wag_tail(self):
print('%s 摇尾巴' % self.name)
pass
pass
d1 = Dog('旺财', '中华田园犬', 'male')
print(d1)
# <__main__.Dog object at 0x00000000026D9BA8>
# __dict__查看对象的属性字典
# 对象只保存数据属性,不保存方法
print(d1.__dict__)
# {'name': '旺财', 'type': '中华田园犬', 'gender': 'male'}
# 调用对象的属性
# 对象有的(__init__)直接调用 name
# 对象没有的,到外面的作用域类里面去找 fur
print(d1.name, d1.fur)
# 对象调用方法是调用类的方法,调用时将对象传给self参数
Dog.bark(d1)
d1.bark()
# 旺财 汪汪!
面向对象属性的查改增删操作
Python的面向对象支持属性的查改增删。类的属性、对象的属性都可以查改增删。
以前用Java知道可以查、改属性,学了Python才知道属性还可以增、删,而且是数据属性和方法属性的查改增删,真牛逼。
类属性的查改增删
我们做查改增删,对比类的属性字典__dict__的内容和调用情况。直接看代码例子……
class Dog:
"""
这是一个狗类
"""
fur = 'property fur'
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品种
self.gender = gender
pass
def bark(self):
print('%s 汪汪!' % self.name)
pass
def wag_tail(self):
print('%s 摇尾巴' % self.name)
pass
def eat_food(self, food):
print('%s 吃 %s' % (self.name, food))
pass
pass
d1 = Dog('旺财', '中华田园犬', 'male')
d2 = Dog('123', '中华田园犬', 'male')
# ========== 类属性的查 ==========
print(Dog.fur)
# property fur
print('查询', Dog.__dict__)
# 查询
# {'__module__': '__main__',
# '__doc__': '\n 这是一个狗类\n ',
# 'fur': 'property fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# 'eat_food': <function Dog.eat_food at 0x0000000003BAA7B8>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>}
# ========== 类属性的改 ==========
Dog.fur = 'modify fur'
print(Dog.fur)
# 改方法属性
def test(self):
print('%s 是 %s 品种' % (self.name, self.breed))
pass
# 方法属于类,可以在类上改方法属性
# 修改后调用eat_food其实调用的是test
Dog.eat_food = test
d1.eat_food()
# 旺财 是 中华田园犬 品种
# 可以看到eat_food的地址已经发生了变化,变成了test函数的地址
print(test)
# <function test at 0x00000000028950D0>
print('修改', Dog.__dict__)
# 修改
# {'__module__': '__main__',
# '__doc__': '\n 这是一个狗类\n ',
# 'fur': 'modify fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# 'eat_food': <function test at 0x00000000028B50D0>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>}
# ========== 类属性的增 ==========
# 增加数据属性——项圈
Dog.ring = 'leather'
# 增加方法属性
def play_ball(self):
print('%s 玩球' % self.name)
pass
# 方法属于类,可以在类上增加方法属性play_ball
Dog.play_ball = play_ball
d1.play_ball()
# 旺财 玩球
# 可以看到增加属性以后__dict__里面增加了ring属性、play_ball方法
print('增加', Dog.__dict__)
# 增加
# {'__module__': '__main__',
# '__doc__': '\n 这是一个狗类\n ',
# 'fur': 'modify fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# 'eat_food': <function test at 0x00000000028C50D0>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>,
# 'ring': 'leather',
# 'play_ball': <function play_ball at 0x0000000003BAA7B8>}
# ========== 类属性的删 ==========
# 删除数据属性
del Dog.ring
# 删除方法
# 方法属于类,可以在类上删除
del Dog.eat_food
# 注意比较删除前后的属性字典,删除ring以后就没有ring了,删除以后就没有eat_food方法了
print('删除', Dog.__dict__)
# 删除
# {'__module__': '__main__',
# '__doc__': '\n 这是一个狗类\n ',
# 'fur': 'modify fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>,
# 'play_ball': <function play_ball at 0x0000000003BAA7B8>}
对象属性的查改增删
我们做查改增删,对比对象的属性字典__dict__的内容和调用情况。直接看代码例子……
class Dog:
"""
这是一个狗类
"""
fur = 'property fur'
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品种
self.gender = gender
pass
def bark(self):
print('%s 汪汪!' % self.name)
pass
def wag_tail(self):
print('%s 摇尾巴' % self.name)
pass
def eat_food(self, food):
print('%s 吃 %s' % (self.name, food))
pass
def play_ball(self, ball):
print('%s 玩 %s' % (self.name, ball))
pass
pass
d1 = Dog('旺财', '中华田园犬', 'male')
d2 = Dog('123', '中华田园犬', 'male')
# ========== 对象属性的查 ==========
print(d1.name)
print(d2.name)
# 旺财
# 123
d1.play_ball('篮球')
d2.play_ball('篮球')
print(d1.play_ball)
# 旺财 玩 篮球
# 123 玩 篮球
# <bound method Dog.play_ball of <__main__.Dog object at 0x0000000003B9D8D0>>
# 打印d1.play_ball实际访问的是类Dog.play_ball
print(d1.__dict__)
print(d2.__dict__)
# {'name': '旺财', 'breed': '中华田园犬', 'gender': 'male'}
# {'name': '123', 'breed': '中华田园犬', 'gender': 'male'}
# ========== 对象属性的增 ==========
# 增加数据属性
d1.age = 10
print(d1.__dict__)
print(d2.__dict__)
# {'name': '旺财', 'breed': '中华田园犬', 'gender': 'male', 'age': 10}
# {'name': '123', 'breed': '中华田园犬', 'gender': 'male'}
# 对象可以增加方法属性吗?
# 理论和技术上可以这么做,那么增加的方法就只属于这个对象。
# 这违背了对象实例只有数据属性的原则,不要这么做!!!
# ========== 对冲属性的改 ==========
d1.age = 17
print(d1.age)
# 17
# ========== 对象属性的删 ==========
del d1.age
del d2.name
print(d1.__dict__)
print(d2.__dict__)
# {'name': '旺财', 'breed': '中华田园犬', 'gender': 'male'}
# {'breed': '中华田园犬', 'gender': 'male'}
关于类、对象属性容易混淆额或忽略的地方的说明
挺绕的,直接看代码例子里的注释吧……
fur = 'yellow'
class Dog:
"""
这是一个狗类
"""
fur = 'property fur'
li = ['a', 'b']
li2 = ['x', 'y']
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品种
self.gender = gender
print('--->', fur)
pass
pass
# __init__里面既不是self的也不是作用域内的ring
# 那么就是全局的ring,和类已经没有关系了
d1 = Dog('旺财', '中华田园犬', 'male')
d2 = Dog('123', '中华田园犬', 'male')
# ---> yellow
# ---> yellow
# 数据属性就是属于各自的
Dog.fur = 'yellow'
d1.fur = 'white'
d2.fur = 'gray'
print('类的毛:', Dog.fur)
print('对象d1的毛:', d1.fur)
print('对象d2的毛:', d2.fur)
# 类的毛: yellow
# 对象d1的毛: white
# 对象d2的毛: gray
# 由于li是类的属性
# 通过d1调用li的append并没有给对象新增任何属性
# 所以修改的就是类的li,因此li都是['a','b','c']
d1.li.append('c')
print(Dog.li)
print(d1.li)
print(d2.li)
# ['a', 'b', 'c']
# ['a', 'b', 'c']
# ['a', 'b', 'c']
# 由于li2也是类的属性
# 但是,d2.li2=是给d1对象新增了一个属性,这与上面append不同
# 所以修改的是d2.li2的属性
d1.li2 = [1, 2, 3]
print(Dog.li2)
print(d1.li2)
print(d2.li2)
# ['x', 'y']
# [1, 2, 3]
# ['x', 'y']
# 此时,再对d1和d2的li2操作,已经有本质的不同了
# d1的li2是d1自己的
# d2的li2是类的
# 这一点可以从对象的__dict__可以看出
d1.li2.append('c')
d2.li2.append('z')
print(Dog.li2)
print(d1.li2)
print(d2.li2)
print(d1.__dict__)
print(d2.__dict__)
# ['x', 'y', 'z']
# [1, 2, 3, 'c']
# ['x', 'y', 'z']
# {'name': '旺财', 'breed': '中华田园犬', 'gender': 'male', 'fur': 'white', 'li2': [1, 2, 3, 'c']}
# {'name': '123', 'breed': '中华田园犬', 'gender': 'male', 'fur': 'gray'}