欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python 历险记之面向对象——一个 Java 程序员的告白(二)

程序员文章站 2022-03-23 16:40:38
前言 在 "上篇文章" 中,我使用了与 java类比 以及 代码实例 的方式涉及了 python 3 中 string , 数据结构(Dict, List, 元组) 等重要的主题。 今天我会继续探险,去征服 python 3 中的面向对象 , let's go 让我们出发吧! 类和对象 刚接触 py ......

前言

在 中,我使用了与 java类比 以及 代码实例 的方式涉及了 python 3 中 string, 数据结构(dict, list, 元组)等重要的主题。
今天我会继续探险,去征服 python 3 中的面向对象, let's go 让我们出发吧!

类和对象

刚接触 python 中的类和对象,我也和大多数小伙伴一样迷茫,不知道它和我所熟知的 java 都有什么异同点,为此我还提出了一大堆问题

  1. 如何创建和实例化类?
  2. 是否和 java 一样有访问修饰符,分为几个级别?
  3. 构造函数该怎么写?
  4. 怎么进行 class 的继承?

下面就一一来探索这些疑惑。

类的定义和实例化

在 java 中要创建一个类就必须要使用 class 关键字,要将类实例化,创建一个对象,可以使用 new 关键字。在 python 中是怎么样的呢?

先看代码

class person():
  """这个叫做定义体,用来解释类的用途"""

print(person) #  <class '__main__.person'>
# 由于是在程序顶层定义的,它的全名就是 '__main__.person'

person = person() 
print(person) #  <__main__.person object at 0x000000000219a1d0>

要定义一个类(class) 只要将 class 关键字放在前面即可,类内部也可以像 java 似的定义变量和函数,这个后面再看。

实例化一个类,也就是创建一个对象,并不需要使用 new 关键字,只需将 class 当做函数来调用就可以啦,是不是比 java 简洁不少。

了解了定义和实例化类,还有两个问题:

  1. 要判断一个对象是不是某个类的实例该怎么做呢?用 isinstance
print(isinstance (person, person)) # true
  1. 判断对象是什么类型,该怎么做? 用 type
print(type(person)) # <class '__main__.person'>

属性

上面的代码,光有一个空对象是干不了任何事情的,我们也要像 java 一样为其定义属性和方法。
java 是不能动态定义一个变量的,必须要把它放在 class 中预先定义好才可以用;而在 python 中这却不是问题,不信看代码~

class person():
  """这个叫做定义体,用来解释类的用途"""

person = person() 
person.age = 5
print(person.age)

虽然在对 person class 定义时没有任何属性的声明,但在实例化后依然可以添加 age 属性,而且也并没有看到如 java 中 public, private 等访问修饰符的存在, python 中有没有这些概念呢?还真有,变量默认就是 public 公有的,如果 在变量名前添加两个下划线,这样就会认为是 private 私有变量了,直接访问是不可以的。看下面代码

class person():
  """这个叫做定义体,用来解释类的用途"""
  gender = 'male'
  __age = 5

person = person() 
print(person.gender) # male
print(person.__age) # attributeerror: 'person' object has no attribute '__age'

上面代码中,在打印 __age 时会报错,告知没有找到这个属性,其实就是 由于使用双下划线做前缀使其变成私有变量了。
函数名是不是也有私有函数,是不是也在前面加双下划线呢 ?猜的没错,这个我们后面再了解。

既然 python 对象的属性操作如此灵活,可以动态添加,那用户在使用时就可能会碰到一些异常。
比较典型的就是,访问一个不存在的属性,会抛出 attributeerror。对这种情况有两种方式可以处理:

  1. 预先使用内置函数 hasattr 判定对象是否拥有该属性(记住,只对公有变量有效哦~)
  2. 使用 try 语句处理
class person():
  """这个叫做定义体,用来解释类的用途"""
  gender = 'male'
  __age = 5

person = person() 

print(hasattr(person, 'gender')) # true
print(hasattr(person, 'name')) # false
print(hasattr(person, '__age')) # false

try:
  name = person.name
except attributeerror:
  name = 'unknown'

print(name)

方法

什么是方法?方法和函数有什么区别?在我就介绍了好多 string 的方法,为什么叫做方法,而不叫做 string 的函数呢?一起来了解下~

  • 函数是指可以执行某种运算,可以通过名字来调用的一段语句的组合
  • 方法是特殊的函数,是跟一个对象或类相关联的
  • 方法是书写在类的定义之中,明确表示和类之间关系的
  • 在调用方法时,前面需要加上类名(函数调用语法)或者实例化的对象名(方法调用语法)

静态方法和普通方法

调用方法分为两种形式,分别是

  • 函数调用语法(静态方法)
  • 普通方法(动态方法)

先看第一种函数调用语法,这其实和 java 中的静态方法是一样的,只是前面不需要 static 关键字。

class person:
  def print_person(person):
    print('name: %s, gender%s, age:%d' % (person.name, person.gender, person.age))


person = person()
person.name = 'tom'
person.gender = 'male'
person.age = 10

person.print_person(person)

函数调用语法的方式其实和单纯的函数调用,区别是不大的,因为方法前面的 class 对它没起什么作用,活动主体 依然是方法。

再看另外一种 方法调用语法,而这次的主体则是调用该方法的 对象

class person:
  __name = 'tom'
  __gender = 'male'
  __age = 10

  def print_person(self):
    print('name: %s, gender:%s, age:%d' % (self.__name, self.__gender, self.__age))


person = person()
person.print_person()

细心的同学会发现这里在定义方法时形参为 self, 而在调用方法时却没有任何入参。
那这个 self 是什么呢?

如果类比 java 的话,这个 self 可以看作是 this, 其实就是对当前对象的引用。 java 中定义方法时不必将其做入参。而这个 self 在 python 中则是必须声明的,在调用的时候则不必传入。

注意,这个 self 可不是关键字哦,只要占据方法形参的头把交椅,你可以用任何名字。

构造函数该怎么写?

在 java 中构造函数是与类同名的,而且会伴随着实例化的动作而执行。在 python 中呢?

python 中的构造函数叫做 init 方法,全名是 __init__
具体看下面代码

class person():
  __gender = 'male'
  __age = '0'

  def __init__(self, gender='male', age=0):
    self.__gender = gender
    self.__age = age

person1 = person('female', 10)
person2 = person()
person2 = person('male')

作为实例方法, self 入参当然少不了,其他参数就按照顺序排开,若参数不够,就用默认值来代替。

str 方法

在java 中, 我们一般会覆盖 tostring() 方法来返回对象中包含的值得关注的信息。 python 中也有这样一个方法,叫做 __str__

class person:
  __name = 'tom'
  __gender = 'male'
  __age = 10

  def __str__(self):
    return ('name: %s, gender:%s, age:%d' % (self.__name, self.__gender, self.__age))


person = person()
print(person)

作为最佳实践的一部分,建议你在每个创建的类中都覆盖这个方法。

多态

还记得面向对象的几个特征吗?封装性,继承性,多态性。嗯,来聊下 python 对多态的实现。

什么叫做多态?

在 java 中,如果在一个 class 中有多个函数,函数名相同而参数不同(个数或类型不同),就叫做多态。

而在 python 中, 多态的概念则更进一步,对于同一个函数,如果能够处理多种类型的数据,也叫做多态。

tuple_list = [(1, 2,), (2, 3,), (4, 5)]
list = [1, 2, 3, 4]
dict1 = {
  'a' : 1,
  'b' : 2
}


def printsomething(something):
  for i in something:
      print(i)

print(tuple_list)
print(dict1)
print(list)

printsomething 一个函数可以同时打印元组,列表以及字典,充分发挥代码复用的功效,是不是很方便。

继承

聊完了多态,再来看看面向对象的另一个特征:继承性。

什么是继承?继承就是定义好了一个类 a(父类);再定义一个新类 b(子类),类 b 拥有类 a 的方法和属性,并且又定义了新的属性和方法。类 a 称为父类,类 b 称为子类。

java 中定义两个类的继承关系,使用 extends 关键字实现,在 python 中呢?

class father:
  """ 这是一个父类 """
  __age = 45


class son(father):
  """ 这是一个子类 """

python 中不需要加关键字来说明继承关系,只需要将父类的名称放在括号中就可以了,看起来要比 java 简洁一些。

父类和子类的初始化函数调用

前面讲过, python class 中可以定义自己的初始化函数,在实例化的时会被调用。那如果父类和子类都有初始化函数或者父类有而子类没有,那初始化函数该如何执行呢?这里分为三种情况来说明,先来看第一种。

第一种情况,

父类有 init 而子类没有, 这时父类的初始化函数会被默认调用

class father():
  """ 这是一个父类 """
  def __init__(self, age):
    print("father's init function invoke")
    self.__age = age

class son(father):
  """ 这是一个子类 """

son = son(5)

这里要注意,父类中需要的 age 参数一定要传进去哦,要不然会报错的。

第二种情况

父类,子类都有 init ,而子类没有显式调用父类的 init 方法时,父类初始化函数是不会被调用的

class father():
  """ 这是一个父类 """
  def __init__(self, age):
    print("father's init function invoke")
    self.__age = age

  def get_age(self):
    return self.__age

class son(father):
  """ 这是一个子类 """
  def __init__(self, age):
    print("son's init function invoke")
    self.__age = age


son = son(5) # son's init function invoke
print(son.get_age()) # attributeerror: 'son' object has no attribute '_father__age'

细心的同学会发现,代码中的最后一句报错了,表示 son 对象没有 father 类的 __age 变量。这是因为

  • 父类的初始化函数没有执行,父类的 __age 变量则没有初始化
  • get_age 函数是被子类从父类继承来的,返回的是父类的 __age 变量

那我要是想解决这个错误,该怎么做呢?有两种方法

  1. 在子类 son 的初始化函数中显式调用父类 father 的初始化函数
  2. 在子类 son 中重新定义个 get_age 方法,这样就会覆盖父类的同名方法,返回的是子类的 _age 变量

第二种方法就不贴代码了,感兴趣的话可以试试。重点来看第一种方法,这就引出了第 3 种情况。

第三种情况

子类在自己定义的 init 方法中,显式调用父类的 init 方法,父类和子类的属性都会被初始化

class father():
  """ 这是一个父类 """
  def __init__(self, age):
    print("father's init function invoke")
    self.__age = age 

  def get_age(self):
    return self.__age

class son(father):
  """ 这是一个子类 """
  def __init__(self, age):
    print("son's init function invoke")
    self.__age = age
    super(son, self).__init__(age + 25)

  def get_age(self):
    return self.__age

  def get_father_age(self):
    return super(son, self).get_age()
son = son(5) 
# son's init function invoke
# father's init function invoke
print(son.get_father_age()) # 30
print(son.get_age()) # 5

看到代码中是怎么调用父类的初始化函数吗? 对,用的是 super

java 中也有 super 关键字,表示对父类的指代, python 的 super 是怎么用的,原理是什么?我们来看下。

super

下面说明的只针对 python 单继承的情况,多继承这里暂不涉及,有兴趣的同学可以自行充电。

在单继承中,super 也可以看做对其父类的指代,它的使用场合就是用来调用父类的方法:

  1. 调用父类的 __init__方法
  2. 实现了和父类相同的功能,还需要调用父类的方法

它的写法是 super(son,self).xxx, 当然也可以写成 super() 这种简写的形式。
来看代码

class father():
  """ 这是一个父类 """
  def __init__(self, age):
    print("father's init function invoke")
    self.__age = age 

  def get_age(self):
    return self.__age

class son(father):
  """ 这是一个子类 """
  def __init__(self, age):
    print("son's init function invoke")
    self.__age = age
    super(son, self).__init__(age + 25)

  def get_age(self):
    return self.__age

  def get_father_age(self):
    return super(son, self).get_age()
son = son(5) 
# son's init function invoke
# father's init function invoke
print(son.get_father_age()) # 30
print(son.get_age()) # 5

通过代码来窥探下它的执行原理,以 super(son, self).get_age() 为例

  1. selfson 的一个实例, superself 转化为父类 father 的一个实例对象
  2. 因为 self 经过了转化, 那它得到的 __age, 也是父类初始化时得到的 __age

结语

看到这里,不知您对 python 的面向对象有了多少理解,反正我是理解了不少,哈哈。如果有疑问和建议,欢迎留言交流,我将仔细阅读,认真回复。

下篇文章中会涉及到 文件, json xml 处理 处理等主题,敬请期待~