python 零基础入门教程第 8 章. 面向对象基础

发布于 更新于
本文目录
  1. 面向对象
  2. 1. 类
  3. 1.1 类的概念
  4. 1.2 类的定义
  5. 案例
  6. 2. 对象
  7. 2.1 对象的概念
  8. 2.2 实例化
  9. 案例
  10. 3. 属性
  11. 3.1类属性
  12. 3.1.1类属性的定义
  13. 案例:
  14. 3.1.2 类属性的访问
  15. 案例:
  16. 3.2 对象属性
  17. 3.2.1 对象属性的定义
  18. 案例:
  19. 3.2.2 对象属性的访问
  20. 案例:
  21. 4. 方法
  22. 4.1 对象方法
  23. 4.1.1 对象方法的定义
  24. 案例:
  25. 4.1.2 对象方法的调用
  26. 4.2 类方法
  27. 案例
  28. 4.3 特殊方法(魔术方法)
  29. __init__
  30. 案例:
  31. __str__
  32. 案例:
  33. 4.4 静态方法
  34. 案例:
  35. 5. 类的继承
  36. 5.1 继承
  37. 案例:
  38. 5.2 重写
  39. 5.3 super方法
  40. 5.4 多态
  41. 5.5 私有化
  42. 6.自省与反射机制
  43. 6.1 自省
  44. type
  45. isinstance
  46. issubclass
  47. dir
  48. 6.2 反射
  49. hasattr
  50. getattr
  51. setattr
  52. delattr

技术文章测试开发

前面我们讲到基本数据类型用来表示最常见的信息。但是信息有无穷多种,为了更好的表达信息,我们可以创建自定义数据类型。

面向对象

前面我们讲到基本数据类型用来表示最常见的信息。但是信息有无穷多种,为了更好的表达信息,我们可以创建自定义数据类型。

1. 类

1.1 类的概念

一种数据类型就是类。例如整数,浮点数,字符串。

1.2 类的定义

python中通过关键字class可以定义一个自定义数据类型,基本语法如下:

class 类名:
	属性
	方法

注意:python中类名规则同变量,一般使用大驼峰来表示。

案例

例如:创建一个Point类用于表示平面坐标系中的一个点

class Point:
    """
    表示平面坐标系中的一个点
    """
print(Point)

<class '__main__.Point'>

2. 对象

2.1 对象的概念

某种数据类型的一个具体的数据称为这个类的一个对象或者实例。通过类创建对象叫做实例化。

所谓的面向对象,就是把一些数据抽象成类的思想。

python是一门面向对象的编程语言,python中一切皆对象。

前面学习的函数也是python中的一个类,定义的某个函数就是函数类的一个具体实例。

def func():
    pass
print(type(func))

<class 'function'>

2.2 实例化

除了基本数据类型实例化的过程中用到的特殊的语法规范外,所有自定义类型进行实例化都是通过调用类名来实现的,非常简单,语法如下:

类名(参数)

看起来和调用函数一样。

案例

给上面创建的Point类创建一个实例。

point = Point()
print(type(point))

<class '__main__.Point'>

3. 属性

类和对象的特征数据称为属性。

3.1类属性

类的特征称为类属性。

3.1.1类属性的定义

直接在类中定义的变量(与class语句只有一个缩进),就是类属性。

案例:

给Point类创建一个name属性用来表示名称。

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

3.1.2 类属性的访问

类属性可以直接通过类名和对象以句点法访问,语法格式如下:

类名.类属性名
对象.类属性名

案例:

print(Point.name)  # 直接通过类名访问类属性
point=Point()  # 创建一个实例
print(point.name)     # 通过对象访问类属性

点 点 注意:如果不存在属性则抛出AttributeError的异常

print(Point.a)

AttributeError Traceback (most recent call last)

in <module> ----> 1 print(Point.a)

AttributeError: type object 'Point' has no attribute 'a'

3.2 对象属性

对象的特征数据称为对象属性。

3.2.1 对象属性的定义

对象属性一般定义在构造方法中,详见下面构造方法一节。

通过句点法对象.对象属性以赋值的方式可以直接定义对象属性。

案例:

平面坐标系中的每个点都有x坐标和y坐标,通过类Point创建一个对象表示点(x=1,y=2)

point = Point()
# 通过赋值直接定义对象属性
point.x = 1
point.y = 2

注意:在定义对象属性时如果和类属性同名,那么通过对象将无法访问到类属性。

3.2.2 对象属性的访问

通过句点法对象.对象属性可以访问对象属性。

案例:

访问上面案例中point的x坐标和y坐标

print(point.x)
print(point.y)

1 2 访问对象属性时,首先会检查对象是否拥有此属性,如果没有则去创建对象的类中查找有没有同名的类属性,如果有则返回,如果都找不到则抛出AttributeError的异常

4. 方法

定义在类中的函数称为方法。通过调用的方式的不同,分为对象方法,类方法,静态方法和魔术方法。

4.1 对象方法

定义在类中的普通方法,一般通过对象调用称为对象方法。

4.1.1 对象方法的定义

为了讲清楚对象方法的定义和调用,我们先看下面的案例。

案例:

定义函数my_print,它接收一个Point对象,然后打印这个点的x,y坐标。

def my_print(point):
    print('({},{})'.format(point.x, point.y))

p = Point()
p.x = 1
p.y = 2
my_print(p)

(1,2) 定义函数distance,它接收两个Point对象,然后返回这两个点的距离。

def distance(p1, p2):
    return ((p1.x-p2.x)**2 + (p1.y-p2.y)**2)**0.5

p1 = Point()
p2 = Point()
p1.x = 1
p1.y = 2
p2.x = 3
p2.y = 4
res = distance(p1,p2)
print(res)

2.8284271247461903 观察上面的两个函数,发现它们都接收一个或多个Point的对象作为参数。为了显式的加强这样的联系,我们可以将它们定义在Point的类中。

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def my_print(point):
        print('({},{})'.format(point.x, point.y))

    def distance(p1, p2):
        return ((p1.x-p2.x)**2 + (p1.y-p2.y)**2)**0.5

4.1.2 对象方法的调用

对象方法向属性一样,可以通过句点法进行调用。

类名.方法名(参数)
对象.方法名(参数)

通过类名调用方法时,和普通函数没有区别

# 更新了类,再次实例化对象
point = Point()
point.x = 1
point.y = 2
p1 = Point()
p2 = Point()
p1.x = 1
p1.y = 2
p2.x = 3
p2.y = 4
Point.my_print(point)
res = Point.distance(p1, p2)
print(res)

(1,2) 2.8284271247461903 通过对象调用方法时,对象本身会被隐式的传给方法的第一个参数

point.my_print()
res = p1.distance(p2)
print(res)

(1,2) 2.8284271247461903 因此,定义对象方法会习惯性的把第一个形参定义为self,表示调用对象本身

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def my_print(self):
        print('({},{})'.format(self.x, self.y))

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2)**0.5

4.2 类方法

在类中通过装饰器classmethod可以把一个方法变成类方法。

一个类方法把类自己作为第一个实参,就像一个实例方法把实例自己作为第一个实参。

案例

定义一个类方法base_point用来返回坐标原点。

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def my_print(self):
        print('({},{})'.format(self.x, self.y))

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2)**0.5
    @classmethod
    def base_point(cls):
        bp = cls()
        bp.x = 0
        bp.y = 0
        return bp

通过类本身或者是该类的实例都可以调用类方法。

p = Point()
bp1 = p.base_point()
bp1.my_print()
bp2 = Point.base_point()
bp2.my_print()

(0,0) (0,0) 类方法一般都用来生成特殊对象。

4.3 特殊方法(魔术方法)

在类中可以定义一些特殊的方法用来实现特殊的功能,也称为魔术方法。这些方法一般都以双下划线__开头

__init__

__init__又叫构造方法,初始化方法,在调用类名实例化对象时,构造方法会被调用,类名括号()后的参数会传递给构造方法,对象属性一般在这个方法中定义。

案例:

上面案例中的Point类实例化后,需要手动创建对象属性x和y,这显然容易出错和不规范,正确的做法应该是在构造方法中定义属性x和y

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def my_print(self):
        print('({},{})'.format(self.x, self.y))

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2)**0.5

    @classmethod
    def base_point(cls):
        return cls(0,0)
# 实例化
p1 = Point(1, 2)
p2 = Point(x=3, y=4)
p1.my_print()
p2.my_print()

(1,2) (3,4)

__str__

__str__方法在对象被print函数打印时被调用,print输出__str__方法返回的字符串。

案例:

上面案例中Point类里的my_print方法可以去掉,定义一个__str__方法

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return '({},{})'.format(self.x, self.y)

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2)**0.5

    @classmethod
    def base_point(cls):
        return cls(0,0)
p = Point(2,2)
print(p)

(2,2) 更多的特殊方法详见官方文档

4.4 静态方法

在类中通过装饰器staticmethod可以把一个方法变静态方法。

静态方法不会接收隐式的第一个参数,它和普通的函数一样,只是被封装到类中。

通过类和对象都可以调用。

案例:

在Point类中定义一个静态方法,用来计算两个数的和。

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return '({},{})'.format(self.x, self.y)

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2)**0.5

    @classmethod
    def base_point(cls):
        return cls(0,0)

    @staticmethod
    def sum(x,y):
        return x+y
Point.sum(1,2)

3

p = Point(1,2)
p.sum(3,4)

7

5. 类的继承

类还有一个重要的特性是继承。

5.1 继承

当定义一个类时,可以从现有的类继承,新的类称为子类(Subclass),被继承的类称为基类,父类或超类(Base class,Super class).

子类可以继承父类的属性和方法。

案例:

创建一个类用来表示三维的点。

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return '({},{})'.format(self.x, self.y)

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2)**0.5

    @classmethod
    def base_point(cls):
        return cls(0,0)

    @staticmethod
    def sum(x,y):
        return x+y

class TdPoint(Point):
    """
    表示三维的点
    """

在上面的案例中TdPoint类继承了Point类。对于TdPoint来说Point是它的父类,对于Point类来说TdPoint是他的子类。

print(dir(TdPoint))

['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', 'base_point', 'distance', 'name', 'sum'] 虽然在TdPoint类中没有定义任何的属性和方法,但它自动继承了父类Point的属性和方法。

5.2 重写

在上面的案例中,虽然TdPoint类继承了Point的属性和方法,但是三维的点比二维的点多了一个纬度,所以大部分方法和属性不合适,需要重写。

在子类中定义同名的方法和属性会覆盖父类的方法和属性。

class Point:
    """
    表示平面坐标系中的一个点
    """
    name = '点'

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return '({},{})'.format(self.x, self.y)

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2)**0.5

    @classmethod
    def base_point(cls):
        return cls(0,0)

    @staticmethod
    def sum(x,y):
        return x+y

class TdPoint(Point):
    """
    表示三维的点
    """
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def __str__(self):
        return '({},{},{})'.format(self.x, self.y, self.z)

    def distance(self, p2):
        return ((self.x-p2.x)**2 + (self.y-p2.y)**2 + (self.z-p2.z)**2)**0.5

    @classmethod
    def base_point(cls):
        return cls(0,0,0)

上面的代码中TdPoint类重写了父类中的__init__,str,distance,base_point三个方法

p1 = TdPoint(1,2,3)
p2 = TdPoint(2,3,4)
print(p1)

(1,2,3)

p1.distance(p2)

1.7320508075688772

print(TdPoint.base_point())

(0,0,0)

5.3 super方法

重写了父类方法后如果又要调用父类的方法怎么解决呢?

例如,三维点在计算点与点的距离时,要求同时返回投射到二维平面的点的距离。

class TdPoint(Point):
    """
    表示三维的点
    """
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def __str__(self):
        return '({},{},{})'.format(self.x, self.y, self.z)

    def distance(self, p2):
        d2 = Point.distance(self, p2)
        d3 = ((self.x-p2.x)**2 + (self.y-p2.y)**2 + (self.z-p2.z)**2)**0.5
        return d2, d3

    @classmethod
    def base_point(cls):
        return cls(0,0,0)
p1 = TdPoint(1,2,3)
p2 = TdPoint(2,3,4)
p1.distance(p2)

(1.4142135623730951, 1.7320508075688772) 可以直接通过类名的方式调用对应的方法。但是这种方法的耦合性太大,官方推荐使用super函数。

class TdPoint(Point):
    """
    表示三维的点
    """
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def __str__(self):
        return '({},{},{})'.format(self.x, self.y, self.z)

    def distance(self, p2):
        d2 = super().distance(p2)
        d3 = ((self.x-p2.x)**2 + (self.y-p2.y)**2 + (self.z-p2.z)**2)**0.5
        return d2, d3

    @classmethod
    def base_point(cls):
        return cls(0,0,0)
p1 = TdPoint(1,2,3)
p2 = TdPoint(2,3,4)
p1.distance(p2)

<super: <class 'TdPoint'>, <TdPoint object>>

(1.4142135623730951, 1.7320508075688772) 在具有单继承的类层级结构中,super 可用来引用父类而不必显式地指定它们的名称,从而令代码更易维护。

super()会返回一个代理对象,它会将方法调用委托给父类,这对于访问已在类中被重载的父类方法很有用。

5.4 多态

python是一门动态语言,严格的来说python不存在多态。

def bark(animal):
    animal.bark()

上面的函数bark接收一个对象,并调用了对象的bark方法。对于python来说只要传入的对象有bark方法这个函数就可以执行,而不必去检查这个对象的类型。

class Animal:
    def bark(self):
        print('嗷嗷叫!')

class Dog(Animal):
    def bark(self):
        print('汪汪叫!')

class Cat(Animal):
    def bark(self):
        print('喵喵叫!')

class Duck(Animal):
    def bark(self):
        print('嘎嘎叫!')
dog = Dog()
cat = Cat()
duck = Duck()
bark(dog)
bark(cat)
bark(duck)

汪汪叫! 喵喵叫! 嘎嘎叫! 上面的案例中dog是Dog类型的一个实例,同时它也是Animal的一个实例。但是反过来不成立。

对于静态语言来说函数bark如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,不能调用bark。

dog,cat,duck都是Animal类型,但是它们执行bark后的输出又各不相同。一个类型多种形态,这就是多态。

对于python这样的动态语言来说,则不需要传入的一定是Animal类型,只要它具有一个bark方法就可以了。

class SomeClass:
    def bark(self):
        print('随便叫!')
sc = SomeClass()
bark(sc)

随便叫!

5.5 私有化

python中不存在那种只能在仅限从一个对象内部访问的私有变量。

但是,大多数 Python 代码都遵循这样一个约定:以一个下划线开头的名称 (例如 _spam) 应该被当作是 API 的非公有部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。

class A:
    _arg1 = 'A'

    def _method1(self):
        print('我是私有方法')
a = A()
a._arg1

'A'

a._method1()

我是私有方法 这种以一个下划线开头的属性可以被类和实例调用。

只是在from xxx import * 时不会被导入。

还有一种定义私有属性的方法是以两个下划线开头的名称(例如__spam),这种方式定义的私有变量只能在类的内部访问。

class A:
    __arg1 = 'A'

    def __method1(self):
        print('我是私有方法')
a = A()
a.__arg1

AttributeError Traceback (most recent call last)

in <module> 1 a = A() ----> 2 a.__arg1

AttributeError: 'A' object has no attribute '__arg1' 这种限制访问的原理是,以双下划线开头的属性名(至少带有两个前缀下划线,至多一个后缀下划线)会被改写成_classname__spam,所以在类外部通过原名称反问不到,但在类的内部使用原名称可以访问。

a._A__arg1

'A'

6.自省与反射机制

6.1 自省

在日常生活中,自省(introspection)是一种自我检查行为。

在计算机编程中,自省是指这种能力:检查对象以确定它是什么类型、它有哪些属性和哪些方法。自省向程序员提供了极大的灵活性和控制力。

type

type函数可以返回一个对象的类型

type(1)

int

isinstance

检查一个对象是否是某个或某些类型的实例

isinstance(1,int)

True

issubclass

检查一个类是否是某个或某些类的子类

issubclass(bool, int)

True

dir

返回一个传入对象的属性名和方法名的字符串列表

print(dir(1))

['abs', 'add', 'and', 'bool', 'ceil', 'class', 'delattr', 'dir', 'divmod', 'doc', 'eq', 'float', 'floor', 'floordiv', 'format', 'ge', 'getattribute', 'getnewargs', 'gt', 'hash', 'index', 'init', 'init_subclass', 'int', 'invert', 'le', 'lshift', 'lt', 'mod', 'mul', 'ne', 'neg', 'new', 'or', 'pos', 'pow', 'radd', 'rand', 'rdivmod', 'reduce', 'reduce_ex', 'repr', 'rfloordiv', 'rlshift', 'rmod', 'rmul', 'ror', 'round', 'rpow', 'rrshift', 'rshift', 'rsub', 'rtruediv', 'rxor', 'setattr', 'sizeof', 'str', 'sub', 'subclasshook', 'truediv', 'trunc', 'xor', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes'] python中的自省函数有很多,凡是可以检查对象状态的函数都可以称为自省函数。

6.2 反射

反射就是动态的操作对象。

简单的讲就是根据字符串形式的属性名方法名操作对应的对象。

hasattr

检查一个对象是否有给定名称的属性

hasattr([1,2,3],'append')

True

getattr

返回一个对象给定名称的属性

getattr(x,'y') 等价于 x.y

class Point:
    name = '点'
getattr(Point,'name')

'点'

setattr

给一个对象添加一个给定名称的属性

setattr(x, 'y', v) 等价于 x.y = v

setattr(Point,'x',1)
Point.x

1

delattr

删除对象的一个给定名称的属性

delattr(x, 'y') 等价与 del x.y

delattr(Point, 'x')
Point.x

AttributeError Traceback (most recent call last)

in <module> ----> 1 Point.x

AttributeError: type object 'Point' has no attribute 'x' 自省和反射机制的理解需要大量的阅读源码。

来源:testingpai

继续阅读