Python学习笔记 面向对象编程

类和对象

定义类

Python支持面向对象编程,下面是一个例子。我们可以看到,在Python中声明类和其他语言差不多。不过实际上差别还是挺大的。

首先,Python没有严格意义上的构造函数,只有一个__init__(self,XXX)函数,该函数和构造函数的功能差不多,用来初始化对象的状态。之后创建对象的时候,直接使用类名和参数列表来创建,这样会调用初始化函数来创建对象。

特别要提一点,所有的Python类的实例函数的第一个参数必须是self,这个参数类似于Java的this关键字,指代当前对象。如果我们调用类上的方法a.f(),那么a这个实例就会传递给self参数。

下面介绍一下Python的实例字段。实例字段使用self.XXX来定义。Python不能像Java那样静态的声明字段。如果有需要,直接使用self.加字段名即可,这也是动态语言的一个特点。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'Person(name:{self.name},age:{self.age})'


person=Person('yitian',24)
print(person)

上面这个例子还定义了一个__str__(self)函数,这个函数和Java的toString()函数类似,当输出的时候就会自动调用这个函数将对象转换为可读的字符串形式。Python中还有很多__XXX__形式的惯例函数,肩负着不同的职责。

共享字段和私有字段

首先需要说明,Python中没有publicprivate这样的字段访问修饰符,也就是说只要你想,你可以调用对象上的任意字段和方法。这里说的都只是一种编码的契约,我们在编写Python类的时候也要遵循这些契约,才能写出合格的代码来。

前面已经说了,实例字段使用self.来访问。如果在类中编写没有self的变量,那么这些变量就是类变量,可以在该类的所有对象之间共享。这个概念类似Java的静态字段。下面的population就是一个共享字段的例子。

class Person:
    population = 0

    def __init__(self, name, age):
        self.name = name
        self.age = age
        Person.population += 1

    def __str__(self):
        return f'Person(name:{self.name},age:{self.age})'


yitian = Person('yitian', 24)
zhang3 = Person('zhang3', 25)
print(yitian)
print(f'population:{Person.population}')

最后来说说私有字段。私有字段惯例上需要添加下划线_前缀。虽然这些“私有变量”也可以在类外边访问,但是我们千万不要这么做。私有字段作为类的内部实现,随时可能存在变化的可能,不应该向外部暴露。我们的代码中也不应该依赖其他类库的私有变量。

结构体

有时候我们可能需要结构体或者数据类这一概念,也就是将相关的变量封装到一个类中。在Python中可以定义一个空类,然后创建对象,并动态赋值。

print('--------------结构体--------------')


class StudentInfo:
    pass


info = StudentInfo()
info.name = 'yitian'
info.age = 24
info.gender = 'male'

print(f'info({info.name},{info.age},{info.gender})')

继承

单继承

支持定义类的语言一般也都支持继承,不然要这么个功能有什么用。如果要继承某个或某些类,在类定义上使用括号指定要继承的基类。如果需要访问基类的成员,使用基类名加点访问符.来访问。

class Student(Person):
    def __init__(self, id, name, age):
        Person.__init__(self, name, age)
        self.id = id

    def __str__(self):
        return f'Student(id:{self.id},name:{self.name},age:{self.age})'

    def introduce_myself(self):
        print(f"I'm {self.name}, I'm {self.age} years old student.")


xiaoming = Student(1, 'xiaoming', 14)
print(xiaoming)
xiaoming.introduce_myself()

继承层次

按照C++的概念,Python类的所有函数都是虚的,也就是说在子类中重写的所有函数,都会父类的同名函数。如果需要调用父类的版本,需要使用父类名.XXX的方式来访问。例如,如果我们要访问xiaoming的父类自我介绍。就需要使用下面的语句。

# 调用父类版本
Person.introduce_myself(xiaoming)

Python提供了两个内置函数isinstanceissubclass来帮我们判断类的继承关系。用法很简单,下面的结果依次是:真真真假。

print('--------------继承关系--------------')

print(f'xiaoming is Student:{isinstance(xiaoming,Student)}')
print(f'xiaoming is Person:{isinstance(xiaoming,Person)}')
print(f'Student is Person:{issubclass(Student,Person)}')
print(f'Person is Student:{issubclass(Person,Student)}')

多重继承

最后再来说说多重继承。多重继承的类签名类似下面这样。当我们访问子类的成员时,Python会先查找子类中存不存在该成员。如果不存在的话在查找父类,如果父类不存在就查找父类的父类,直到查到头为止。如果到这时候还没查找到就会抛出异常。

对于多重继承的话,这个过程可以简单的看成从左到右的、深度优先的查找过程:如果子类中不存在该成员,就先从Base1开始查找,如果Base1和它的所有父类都没有,再从Base2开始查找,以此类推。当然实际情况略微复杂一点,因为Python会检查类继承层次是否存在相同的父类,并确保相同的父类只访问一次。

class DerivedClassName(Base1, Base2, Base3):

迭代器和生成器

迭代器

在很多编程语言中都有迭代器的概念,迭代器可以在for-loop循环中使用。一般情况下迭代器会有next()hasNext()等类似的方法,确定什么时候应该停止迭代,什么时候返回元素。

在Python中需要使用__next__(self)函数来执行迭代,如果到了末尾则需要抛出StopIteration异常。如果编写了__next__(self)函数,我们就可以让__iter__(self):函数返回自身。这样一个迭代器就写好了,我们可以在for循环等地方使用了。

print('--------------迭代器--------------')


class IterableObj:
    def __init__(self, data):
        self.data = data
        self.index = -1

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == len(self.data) - 1:
            raise StopIteration
        self.index += 1
        return self.data[self.index]


obj1 = IterableObj([1, 2, 3])
for i in obj1:
    print(i, end=' ')
print()

Python还包含了两个内置函数iter()next()用于创建迭代器和执行迭代。下面是使用列表迭代的例子。

list1 = [1, 2, 3]
iter1 = iter(list1)
e1 = next(iter1)
e2 = next(iter1)
e3 = next(iter1)

print('List:', e1, e2, e3)

生成器

迭代器虽然使用比较简单,但还是挺麻烦的。我们可以使用生成器更简单的创建迭代器。生成器其实就是一个函数,不过这个函数比较特殊,它不使用return返回结果,而是使用yield返回一系列值。当我们在循环中或者使用next()函数调用生成器的时候,每次调用生成器都会使用yield返回一个值。

print('--------------生成器--------------')


def even_generator(data):
    index = 0
    while index < len(data):
        if data[index] % 2 == 0:
            yield data[index]
        index += 1


integer_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(f'even_generator:{[i for i in even_generator(integer_list)]}')

从这个例子我们可以看到,生成器确实比迭代器更方便。

生成器表达式

生成器表达式其实和列表解析表达式差不多,只不过列表解析表达式使用方括号,而生成器表达式使用小括号。另外,生成器表达式返回的是一个生成器,而列表解析表达式返回的是列表。除此之外,它们在迭代的时候结果完全相同。

不过,由于生成器不是一次性生成所有值,所以当迭代的序列非常大的时候,最好使用生成器表达式而不是列表解析表达式。

print('--------------生成器表达式--------------')

odd_generator = (i for i in range(1, 11) if i % 2 != 0)

odd_list = [i for i in range(1, 11) if i % 2 != 0]

print(f'generator type:{type(odd_generator)}')
print(f'list type:{type(odd_list)}')

结果如下。

--------------生成器表达式--------------
generator type:<class 'generator'>
list type:<class 'list'>

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程

Python基础知识2:字典

字典一种key - value 的数据类型,就像上学用的字典通过拼音查找汉字一样;字典是Python语言中唯一的映射类型。字典对象是可变的,它是一个容器类型,能...

260100
来自专栏Nian糕的私人厨房

ECMAScript6 解构赋值

在 ES6 中,关于解构的含义为:允许按照一定模式,从数组和对象中提取值,对变量进行赋值,而数组、对象和字符串,都能通过这种方式进行赋值

8740
来自专栏逆向技术

C语言第九讲,结构体

31820
来自专栏marsggbo

strtol函数 将字符串转换为相应进制的整数

+----------------+ |   strtol   | +----------------+ i.e. string to...

24470
来自专栏鸿的学习笔记

Python对象的比较:is和==

这两个符号在Python的比较判断中应用广泛,但是这两者是有区别的,体现的是对象中的相等和标识符的概念。==符号比较的是两个对象是否相等,而is符号表达的则是标...

8120
来自专栏LanceToBigData

JavaSE(四)之接口、访问控制

上面我们学习了几个修饰符,在开发中经常会用的到,所以必须熟练的掌握。接下来我学习一下接口和访问控制。 一、接口 一系列方法的声明,是一些方法特征的集合,一个接口...

25970
来自专栏一“技”之长

深入理解JavaScript函数 原

    从功能上理解,函数是一组可以随时运行的语句,是一段代码块,也是所谓的子程序。在JavaScript中,函数实质上也是一种对象,是Function对象。函...

7410
来自专栏十月梦想

变量和常亮

在函数内,变量的使用需要声明常量的作用域globay变量名(全局作用)local(局部)static(静态)

8820
来自专栏Java帮帮-微信公众号-技术文章全总结

String中的null,以及String s;等区别详解

1、判断一个引用类型数据是否null。 用==来判断。 2、释放内存,让一个非null的引用类型变量指向null。这样这个对象就不再被任何对象应用了。等待JVM...

39240
来自专栏猿人谷

const用法小结

常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。因此,定义或说明常类型时必须进行初始化。 概述 1. const有什么...

19870

扫码关注云+社区

领取腾讯云代金券