python笔记:详细介绍python中的面向对象+补充Lambda表达式!

详细介绍python中的面向对象+补充Lambda表达式!

文章目录

    • 一、python面向对象知识补充!
        • 1.1、介绍一下对象
        • 1.2、对象=属性+方法
        • 1.3、面向对象编程
          • 1.3.1、self是什么
          • 1.3.2、初始化方法
          • 1.3.3、共有和私有
        • 1.4、继承
          • 1.4.1、调用未绑定的父类方法
          • 1.4.2、使用super函数
        • 1.5、多重继承
        • 1.6、组合
        • 1.7、销毁对象(垃圾收集)
    • 二、补充Lambda表达式

一、python面向对象知识补充!

1.1、介绍一下对象

首先大家知道把乱七八糟的数据扔进列表里面,这是一种封装,是数据层面的封装;把常用的代码打包成一个函数,这也是一种封装,是语句层面的封装;下面介绍的对象,也是一种封装的思想,不过这种思想显然更加先进一些:对象来源是模拟真实世界,把数据和代码都封装在一起。
打个比方,乌龟就是真实世界的一个对象,那么通常应该如何来描述这个对象呢?是不是把它分为2部分来说呢?

  1. 可以从静态的特征来描述,例如,绿色的、有4条腿,10kg重,有外壳,还有个大嘴巴,这是静态一方面的描述。
  2. 还可以从动态的行为来描述,例如说它会爬,还会吃东西,还会睡觉等等。这些都是行为方面的描述。

1.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

1.3、面向对象编程

1.3.1、self是什么

可能我们会发现对象的方法都会有一个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
1.3.2、初始化方法

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
1.3.3、共有和私有

一般面向对象的编程语言都会区分共有和私有的数据类型,像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的类是没有权限控制的,所有的变量都可以被外部调用的。

1.4、继承

假如现在需要扩展一款游戏,对鱼类进行细分,有金鱼(Goldfish)、鲤鱼(Carp)、三文鱼(Salmon),还有鲨鱼(Shark)。那么我们就再思考一个问题:能不能不要每次都从头到尾去重新定义一个新的鱼类呢?因为我们知道大部分鱼的属性和方法都是相似的,如果你有一种机制可以让这些相似的东西自动传递,那就方便快捷多了。就是下面要讲的继承

python笔记:详细介绍python中的面向对象+补充Lambda表达式!_第1张图片
  • 语法很简单:
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 _ _ 方法。

下面介绍两种可以实现的技术:

  • 调用未绑定的父类方法。
  • 调用super函数。
1.4.1、调用未绑定的父类方法

调用未绑定的父类方法,听起来有些高深,但大家参考下面的代码就能领会了:

class Shark(Fish):
    def __init__(self):
        Fish.__init__(self)
        self.hugry = True

再运行发现鲨鱼就可以成功移动了;这里注意的是这个self并不是父类Fish的实例对象,而是子类Shark的实例对象,所以这里说的未绑定是指并不需要绑定父类的实例对象,使用子类的实例对象替代即可。

  • 可能不是太理解,没关系,这一点不重要!python中有一个更好的方案取代它,就是使用super函数。
1.4.2、使用super函数

super函数能够帮我实现自动找到基类的方法,而且还为我们传入了self参数,这样就不需要做这些事情了。

class Shark(Fish):
    def __init__(self):
        #Fish.__init__(self)
        super().__init__()
        self.hugry = True

运行能得到同样的结果;super函数的超级之处在于你不需要明确给出任何基类的名字,它会自动帮你找出所有基类以及对应的方法。由于你不用给出基类的名字,这就意味着如果需要改变类的继承关系,只要改变class语句里的父类即可,而不必在大量代码中修改所有被继承的方法。

1.5、多重继承

除此之外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。

1.6、组合

前边刚刚介绍了继承的概念,然后又介绍了多重继承,经常看到一些博客大牛介绍到,不到必要的时候不使用多重继承。假如现在遇到这样一个问题:我们有了乌龟类、鱼类,现在要定义一个类叫做水池,水池里面有乌龟和鱼。用多重继承就显得很奇怪,因为水池和乌龟、鱼不是同一个物种,那要怎样才能把它们组合成一个水池的类呢?
在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

1.7、销毁对象(垃圾收集)

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 _ _(),该方法在实例即将被销毁时被调用。
此方法可能用于清理实例使用的任何非内存资源。

  • 如下:这个_ _ 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()
  • 可以参考文档:https://www.cnblogs.com/wupeiqi/p/4766801.html

二、补充Lambda表达式

python中允许使用lambda关键字来创建匿名函数。什么是匿名函数呢?

  • 先定义一个普通的函数:
def ds(s):
	return x * 2 + 1
ds(5)
# 打印结果:5
  • 如果使用lambda语句来定义这个函数,就会变成这样:
lambda x : 2 * x + 1
< function < lambda > at 0x00000000007FCD08 >

python中的lambda表达式非常的精简(符合python的代码风格),基本语法是在冒号(:)左边放原函数的参数,可以放多个参数,用逗号即可;冒号右边是返回值。在上面的例子中我们发现lambda表达式返回的是一个函数对象,如果对它进行操作,只需要进行简单的赋值操作即可:

g = lambda x : 2 * x + 1
g(5)
# 打印结果:11
  • 下面演示lambda表达式带两个参数的例子:
# 普通函数的形式
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

你可能感兴趣的:(Python)