详细介绍python中的面向对象+补充Lambda表达式! |
首先大家知道把乱七八糟的数据扔进列表里面,这是一种封装,是数据层面的封装;把常用的代码打包成一个函数,这也是一种封装,是语句层面的封装;下面介绍的对象,也是一种封装的思想,不过这种思想显然更加先进一些:对象来源是模拟真实世界,把数据和代码都封装在一起。
打个比方,乌龟就是真实世界的一个对象,那么通常应该如何来描述这个对象呢?是不是把它分为2部分来说呢?
python中的对象也是如此,一个对象的特征称为属性,一个对象的行为叫做方法。
class Turtle:
#python中类名字约定以大写字母开头
#特征的描述称为属性,在代码层面来看就是变量
color = 'green'
weight = 10
legs = 4
shell = True
#方法实际就是函数,通过调用这些函数来完成某些工作
def climb(selfs):
print('我正在努力爬!')
def run(self):
print('我正在往前跑!')
def eat(self):
print('我正在吃东西!')
def sleep(self):
print('我要睡觉了亲!')
以上代码定义了对象的特征(属性)和行为(方法),但是还不是一个完整的对象,将定义的这些称为类(Class),需要使用类来创建一个真正的对象,这个对象叫做这个类的一个实例(Instance),也叫实例对象(Instance Object)。
可能还是不太理解,可以这么思考:就好比一个工厂的流水线要生产一系列的玩具,是不是需要先做出这个玩具的模具,然后根据这个模具再进行批量生产,才得到真正的玩具?
再举例一个:盖房子,是不是要现有个图纸,但图纸并不是真正的房子。要根据图纸用钢筋水泥建造出来的房子才能住人,另外根据一张图纸就能盖出很多的房子。因此:类用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
tt = Turtle()
注意:类名后边跟着小括号,这跟调用函数是一样的,所以Python中,类名约定用大写字母开头,函数用小写字母开头,这样更容易区分。另外赋值操作并不是必需的,但是如果没有把创建好的实例对象赋值给一个变量,那这个对象就没办法使用,因为没有任何引用指向这个实例,最终会被python的垃圾收集机制自动回收。
那如果要调用对象里面的方法,使用操作符(.)即可,如下:
tt.climb()
tt.sleep()
tt.eat()
class Turtle:
#python中类名字约定以大写字母开头
#特征的描述称为属性,在代码层面来看就是变量
color = 'green'
weight = 10
legs = 4
shell = True
#方法实际就是函数,通过调用这些函数来完成某些工作
def climb(selfs):
print('我正在努力爬!')
def run(self):
print('我正在往前跑!')
def eat(self):
print('我正在吃东西!')
def sleep(self):
print('我要睡觉了亲!')
tt = Turtle()
tt.climb()
tt.sleep()
tt.eat()
我正在努力爬!
我要睡觉了亲!
我正在吃东西!
Process finished with exit code 0
可能我们会发现对象的方法都会有一个self的参数,那么这个参数到底是什么东西呢?如果之前接触过其它面向对象的编程语言,例如c++,那么很容易对号入座,python中的self就是相当于c++中的this指针。
self到底是什么东西呢?解释一下:如果把类比作一张图纸,那么类实例化的对象才是真正的房子,根据一张图纸可以设计出成千上万的房子,它们长得差不多,但他们都有不同的主人。每个人只能回到自己的家中,所以self这里就是相当于每个房子的门牌号,有了self,你就可轻松的找到自己的房子。
python的self参数也是同一个道理,由同一个类可以生成无数的对象,当一个对象的方法被调用的时候,对象会将自身的引用作为第一个参数传给该方法,那么python就知道操作哪个对象的方法了。
class Ball:
def setName(self, name):
self.name = name
def kick(self):
print('我叫%s,谁在踢我?'%self.name)
a = Ball()
a.setName('zhang')
b = Ball()
b.setName('wang')
c = Ball()
c.setName('li')
a.kick()
b.kick()
c.kick()
我叫zhang,谁在踢我?
我叫wang,谁在踢我?
我叫li,谁在踢我?
Process finished with exit code 0
python对象天生有一些神奇的方法,它们是面向对象的python的一切,它们是可以给你的类增加魔力的特殊方法,如果你的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被python所调用,而这一切都是自动发生的。
python中的这些方法,总是被双下划线所包围,下面介绍一个最基本的特殊方法:_ _ init _ ()方法。通常 _ init _ ()方法称为构造方法,它的魔力体现在只要实例化一个对象,这个方法就会在创建对象的时候自动调用(在c++里你也可以看到类似的东西,叫“构造函数”)。其实实例化对象可以传入参数的,这些参数会自动的传入方法 _ init _ _()中,可以通过重写这个方法来自定义对象的初始化操作。
class Potato:
def __init__(self, name):
self.name = name
def kick(self):
print('我叫%s,谁在踢我'%self.name)
p = Potato('devin zhang')
p.kick()
我叫devin zhang,谁在踢我
Process finished with exit code 0
一般面向对象的编程语言都会区分共有和私有的数据类型,像c++和java中使用public和private关键字,用于声明数据是公有的还是私有的,但是在python中并没有用类似的关键字来修饰。
难道python中的所有的东西都是透明的?也不全是,默认上对象的属性和方法都是公开的,可以通过操作符(.)进行访问:
class Person:
name = 'devin zhang'
p = Person()
print(p.name)
devin zhang
Process finished with exit code 0
python中为了实现私有变量的特征,python内部采用了一种叫做name mangling(名字改编)的技术,在python中定义私有变量只需要在变量名前加“_ _”两个下划线,那么这个函数或变量就会成为私有的了:
class Person:
__name = 'devin zhang'
p = Person()
print(p.__name)
Traceback (most recent call last):
File "/home/zhangkf/tf1/TF/test.py", line 5, in
print(p.__name)
AttributeError: 'Person' object has no attribute '__name'
Process finished with exit code 1
class Person:
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
p = Person('devin zhang')
print(p.getName())
devin zhang
Process finished with exit code 0
但是认真琢磨一下这个技术的名字name mangling(名字改编),那就不难发现其实python只是动了一下手脚,把双下横线开头的变量进行了该名而已。实际上在外部你使用“_ _ 类名 _ _变量名”即可访问双下划线开头的私有变量了:
class Person:
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
p = Person('devin zhang')
print(p.getName())
print(p._Person__name)
devin zhang
devin zhang
Process finished with exit code 0
注意:python目前的私有机制其实是伪私有,python的类是没有权限控制的,所有的变量都可以被外部调用的。
假如现在需要扩展一款游戏,对鱼类进行细分,有金鱼(Goldfish)、鲤鱼(Carp)、三文鱼(Salmon),还有鲨鱼(Shark)。那么我们就再思考一个问题:能不能不要每次都从头到尾去重新定义一个新的鱼类呢?因为我们知道大部分鱼的属性和方法都是相似的,如果你有一种机制可以让这些相似的东西自动传递,那就方便快捷多了。就是下面要讲的继承。
class 类名(被继承的类)
....
被继承的类称为基类、父类、母类、超类;继承者称为子类,一个子类可以继承他的父类的任何属性和方法。例如:
class Parent:
def hello(self):
print('正在调用父类的方法!')
class Child(Parent):
pass
p = Parent()
print(p.hello())
c = Child()
print(c.hello())
正在调用父类的方法!
正在调用父类的方法!
Process finished with exit code 0
需要注意的是: 如果子类中定义与父类同名的方法或者属性,则会自动覆盖父类对应的方法或属性:
class Parent:
def hello(self):
print('正在调用父类的方法!')
class Child(Parent):
def hello(self):
print('正在调用子类的方法!')
p = Parent()
print(p.hello())
c = Child()
print(c.hello())
正在调用父类的方法!
正在调用子类的方法!
Process finished with exit code 0
import random as r
class Fish:
def __init__(self):
self.x = r.randint(0, 10)
self.y = r.randint(0, 10)
def move(self):
# 这里主要演示类的继承机制,就不考虑检查场景边界和移动方向问题
# 假设所有鱼都是一路向西游
self.x -= 1
print('我的位置是:', self.x, self.y)
class Goldfish(Fish):
pass
class Carp(Fish):
pass
class Salmon(Fish):
pass
class Shark(Fish):
def __init__(self):
self.hugry = True
def eat(self):
if self.hugry:
print('吃货的梦想就是天天有肉吃!')
else:
print('吃撑了,吃不下了!')
fish = Fish()
print(fish.move()) #试试小鱼能不能移动
goldfish = Goldfish()
print(goldfish.move())
print(goldfish.move())
print(goldfish.move())
shark = Shark()
shark.eat()
shark.move()
运行结果:
我的位置是: -1 9
None
我的位置是: 0 10
None
我的位置是: -1 10
None
我的位置是: -2 10
None
吃货的梦想就是天天有肉吃!
Traceback (most recent call last):
File "/home/zhangkf/tf1/TF/test.py", line 44, in
shark.move()
File "/home/zhangkf/tf1/TF/test.py", line 11, in move
self.x -= 1
AttributeError: 'Shark' object has no attribute 'x'
Process finished with exit code 1
奇怪!同样是继承Fish类,为什么金鱼(goldfish)可以移动,而鲨鱼(shark)一移动就会报错呢?其实这里抛出异常说的都很清楚了:Shark对象没有x属性。原因是因为在Shark类中,重写了构造方法 _ _ init _ _ 方法,但新的 _ _init _ _方法里面没有初始化鲨鱼的x坐标和y坐标,因此调用基类Fish的 _ _ init _ _ 方法。
下面介绍两种可以实现的技术:
调用未绑定的父类方法,听起来有些高深,但大家参考下面的代码就能领会了:
class Shark(Fish):
def __init__(self):
Fish.__init__(self)
self.hugry = True
再运行发现鲨鱼就可以成功移动了;这里注意的是这个self并不是父类Fish的实例对象,而是子类Shark的实例对象,所以这里说的未绑定是指并不需要绑定父类的实例对象,使用子类的实例对象替代即可。
super函数能够帮我实现自动找到基类的方法,而且还为我们传入了self参数,这样就不需要做这些事情了。
class Shark(Fish):
def __init__(self):
#Fish.__init__(self)
super().__init__()
self.hugry = True
运行能得到同样的结果;super函数的超级之处在于你不需要明确给出任何基类的名字,它会自动帮你找出所有基类以及对应的方法。由于你不用给出基类的名字,这就意味着如果需要改变类的继承关系,只要改变class语句里的父类即可,而不必在大量代码中修改所有被继承的方法。
除此之外python还支持多继承,就是可以同时继承多个父类的属性和方法:
class 类名(父类1,父类2,父类3,...)
class Base1:
def foo1(self):
print('我是devin,我在Base1中!')
class Base2:
def foo2(self):
print('我是wang,我在Base2中!')
class C(Base1, Base2):
pass
c = C()
print(c.foo1())
print(c.foo2())
我是devin,我在Base1中!
None
我是wang,我在Base2中!
None
Process finished with exit code 0
上面就是最基本的多重继承语法。但是多重继承其实很容易导致代码混乱,所以当你不确定是否真的必须使用多重继承的时候,请尽量不要使用它,因为有些时候会出现不可预见的BUG。
前边刚刚介绍了继承的概念,然后又介绍了多重继承,经常看到一些博客大牛介绍到,不到必要的时候不使用多重继承。假如现在遇到这样一个问题:我们有了乌龟类、鱼类,现在要定义一个类叫做水池,水池里面有乌龟和鱼。用多重继承就显得很奇怪,因为水池和乌龟、鱼不是同一个物种,那要怎样才能把它们组合成一个水池的类呢?
在python中其实很简单,直接把需要的类放进实例化就可以了,这就叫做组合:
class Turtle:
def __init__(self, x):
self.num = x
class Fish:
def __init__(self, x):
self.num = x
class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x)
self.fish = Fish(y)
def print_num(self):
print('水池里总共有乌龟:%d只,小鱼%d条!' %(self.turtle.num, self.fish.num))
pool = Pool(1, 10)
print(pool.print_num())
水池里总共有乌龟:1只,小鱼10条!
None
Process finished with exit code 0
Python自动删除不需要的对象(内置类型或类实例)以释放内存空间。 Python定期回收不再使用的内存块的过程称为垃圾收集。
Python的垃圾收集器在程序执行期间运行,当对象的引用计数达到零时触发。 对象的引用计数随着指向它的别名数量而变化。
当对象的引用计数被分配一个新名称或放置在容器(列表,元组或字典)中时,它的引用计数会增加。 当用del删除对象的引用计数时,引用计数减少,其引用被重新分配,或者其引用超出范围。 当对象的引用计数达到零时,Python会自动收集它。
a = 40 # Create object <40>
b = a # Increase ref. count of <40>
c = [b] # Increase ref. count of <40>
del a # Decrease ref. count of <40>
b = 100 # Decrease ref. count of <40>
c[0] = -1 # Decrease ref. count of <40>
通常情况下,垃圾回收器会销毁孤立的实例并回收其空间。 但是,类可以实现调用析构函数的特殊方法_ _ del _ _(),该方法在实例即将被销毁时被调用。
此方法可能用于清理实例使用的任何非内存资源。
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __del__(self):
class_name = self.__class__.__name__
print('class_name', 'destroyed')
pt1 = Point()
pt2 = pt1
pt3 = pt1
print(id(pt1), id(pt2), id(pt3)) # prints the ids of the obejcts
1446780765912 1446780765912 1446780765912
class_name destroyed
Process finished with exit code 0
注意: 理想情况下,应该在单独的文件中定义类,然后使用import语句将其导入主程序文件。
在上面的例子中,假定Point类的定义包含在point.py中,并且其中没有其他可执行代码。
import point
p1 = point.Point()
python中允许使用lambda关键字来创建匿名函数。什么是匿名函数呢?
def ds(s):
return x * 2 + 1
ds(5)
# 打印结果:5
lambda x : 2 * x + 1
< function < lambda > at 0x00000000007FCD08 >
python中的lambda表达式非常的精简(符合python的代码风格),基本语法是在冒号(:)左边放原函数的参数,可以放多个参数,用逗号即可;冒号右边是返回值。在上面的例子中我们发现lambda表达式返回的是一个函数对象,如果对它进行操作,只需要进行简单的赋值操作即可:
g = lambda x : 2 * x + 1
g(5)
# 打印结果:11
# 普通函数的形式
def add(x, y):
return x + y
print(add(3, 4))
# lambda表达式
g = lambda x, y : x + y
print(g(3, 4))
7
7
Process finished with exit code 0