概述
在最初设计的时候,Python就被设计成了支持面向对象的编程语言,因此Python完全能以面向对象的方式编程,而且Python的面向对象方法比较简单
6.1 类和对象
类是面向对象的重要内容,有过Java编程经验的伙伴肯定对此不陌生,我们可以把类当成一种自定义类型,可以使用类来定义变量,也可以用来创建对象
6.1.1 定义类
在面向对象的程序设计中有两个重要概念:类(class)和对象(object,有时也被称作实例),其中类是某一类对象的抽象,例如People,是许许多多的人的抽象,而对象是一个具体的存在,比如马斯克是一个具体的人。
类中定义的各个成员(属性和方法)的顺序没有任何影响,各个成员之间可以互相调用
Python类中最重要的就是属性和方法,属性用于定义该类本身所包含的状态数据,如人的姓名年龄,方法则用于定义该类的行为,如吃饭睡觉
另外需要注意的是:Python是一门动态语言,因此它的类所包含的类属性可以动态增加或删除,程序在类体中为新变量赋值就是增加类变量,程序也可以在任何地方为已有的类增加属性,也可以通过del语句删除已有的类属性
在类中定义的方法默认是实例方法,方法与函数基本相同,区别只是实例方法的第一个参数会被绑定到方法的调用者(该类的实例),因此实例方法至少应该定义一个参数,该参数通常会被命名为self
定义类使用class关键字,里面包含类的属性和方法,如下是一个类定义的例子
例6.1 类的定义使用
#!/usr/bin/python3
class Dog:
name = "Tom" #属性
#方法
def eat(self):
return "eat food"
# 实例化类
x = Dog() #创建对象
# 访问类的属性和方法
print("Dog 类的属性 name 为:", x.name)
print("Dog 类的方法 eat 输出为:", x.eat())
结果:
Dog 类的属性 name 为: Tom
Dog 类的方法 eat 输出为: eat food
6.1.2 构造函数
在类定义的方法中有一个特别的方法:__init__,这个方法被称为构造方法,构造方法用于构造该类的对象,也可以初始化一些属性的值,如果程序员没有为该类显示的定义构造方法,Python会自动为该类定义一个只包含一个self参数的默认的构造方法,具体使用方法如下例子所示:
例6.2 构造方法的使用
#!usr/bin/python3
class Dog:
name = "zhangsan"
age = 20
#这是一个构造方法
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
return self.name + " " + str(self.age) + " eat food"
dog = Dog("Tom", 20) #创建对象
print("name = ", dog.name)
print("age = ", dog.age)
print("eat = ", dog.eat())
结果:
name = Tom
age = 20
eat = Tom 20 eat food
6.1.3 类中的方法调用
在类中的某个方法调用另一个方法时,使用self.xxx()来调用,其中self不可以省略,如下例,eat调用say方法
例6.3
#!usr/bin/python3
class Dog:
name = "zhangsan"
age = 10
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print("eat method")
self.say() #调用say方法
return self.name + " eat food"
def say(self):
print("say method")
dog = Dog("Tom", 20)
dog.eat()
结果:
eat method
say method
另外需要说明的是,自动绑定的self参数并不依赖具体的调用方式,不管是以方法调用还是以函数调用的方式执行,self参数都可以自动绑定,例如下例
例6.4
#!usr/bin/python3
class Dog:
name = "zhangsan"
age = 10
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print("eat method")
dog = Dog("Tom", 20)
dog.eat() #方法的方式调用
print("=============")
foo = dog.eat #将dog.eat()方法赋值给foo
foo() #函数的方式调用
结果:
eat method
=============
eat method
6.2 方法的其他用法
在上文列举了一下类方法的使用,除此之外,类方法还有其他的一些特性
6.2.1 类方法和静态方法
python的类方法和静态方法很相似,它们都推荐使用类来调用,当然使用对象也可以调用,类方法和静态方法的区别在于,Python会自动绑定类方法的第一个参数,类方法的第一个参数通常建议命名为cls,会自动绑定到类本身,而对于静态方法则不会自动绑定
使用@classmethod修饰的方法就是类方法,使用@staticmethod修饰的方法就是静态方法,其使用方式如下
例6.5
#!usr/bin/python3
class Bird:
@classmethod
def fly(cls):
print("class method fly", cls)
@staticmethod
def info(p):
print("static method info", p)
Bird.fly() #使用类名调用类方法,第一个参数会自动绑定
Bird.info("bird") #使用类名调用静态方法,不会进行参数自动绑定
print("===============")
bird = Bird()
bird.fly() #使用对象调用类方法,第一个参数会自动绑定
bird.info("bird2") #使用对象调用静态方法,不会进行参数自动绑定
结果:
class method fly <class '__main__.Bird'>
static method info bird
===============
class method fly <class '__main__.Bird'>
static method info bird2
在本例中出现了@修饰的函数,这就是常说的函数装饰器,函数装饰器可以用来修饰其他函数,当程序使用@函数A装饰另一个函数B时,实际上完成了两步工作
1. 将被修饰的函数B作为参数传给@符号引用的函数A
2. 将函数B替换成第一步的返回值
下面是一个例子,使用funA装饰funB
例6.6
#!usr/bin/python3
def funA(fn):
print("A")
fn()
return "aaaa" #被装饰的函数变成了字符串aaaa
@funA
def funB():
print("B")
print(funB)
结果:
A
B
aaaa
通过@符号来修饰函数是Python的一个很实用的功能,例如它可以在被修饰函数的前面添加一些额外的处理逻辑如权限检查,也可以在被修饰函数的后面添加一些额外的处理逻辑如记录日志,还可以在目标方法抛出异常时进行一些修复操作,这些改变不需要修改被修饰函数的代码,只需要增加一个修饰即可。有过Java编程经验的同学对AOP面向切面编程很熟悉,其实际上实现的也就是这种在被修饰函数之前、之后增加了某种处理逻辑。
下面是一个模拟的权限检查的函数装饰器
例6.7
#!usr/bin/python3
def auth(fn):
def auth_fn(*args):
print("auth start")
fn(*args)
return auth_fn
@auth
def test(a, b):
print("test start, a = %s, b = %s" % (a, b))
test(10, 20)
结果:
auth start
test start, a = 10, b = 20
上面这个程序使用了@auth修饰了test()函数,这会使得test()函数被替换成auth()函数所返回的auth_fn函数,而auth_fn函数的执行流程是1. 先打印auth start,2. 回调被修饰的目标函数,通过这个例子,可以发现auth_fn函数就为被修饰函数添加了一个权限检查的功能
6.2.2 私有方法
有一些方法不希望被外部访问到,这种方法可以定义成私有方法,私有方法不能在外部访问,定义私有方法也是在方法名前面加__,在外部调用私有方法会报错
例6.8
#!usr/bin/python3
class Student:
def __get_name(self):
return "student"
def info(self):
#可以在类体调用私有方法,但不可以在外部调用
print(self.__get_name())
student = Student()
student.info()
print(student.__get_name()) #这行会报错
结果:
student
Traceback (most recent call last):
File "/Users/bytedance/Desktop/code/ai-ocr/test2.py", line 13, in <module>
print(student.__get_name()) #这行会报错
AttributeError: 'Student' object has no attribute '__get_name'
6.3 成员变量
6.3.1 私有属性
同私有方法一样,私有属性也不支持外部访问,访问私有属性会报错
例6.9
#!usr/bin/python3
class People:
__name = "zhangsan"
#私有属性只能内部调用
def info(self):
print("name = ", People.__name)
people = People()
people.info()
print("name = ", people.__name) #这行会报错
结果:
Traceback (most recent call last):
File "/Users/bytedance/Desktop/code/ai-ocr/test2.py", line 11, in <module>
print("name = ", people.__name)
AttributeError: 'People' object has no attribute '__name'
name = zhangsan
6.3.2 使用property函数定义属性
如果为Python类定义了getter、setter等访问器方法,则可使用property()函数将它们定义成属性(相当于实例变量)
property()函数的语法格式如下:
property(fget=None, fset=None, fdel=None, doc=None)
从上面的语法格式可以看出,在使用property()函数时,可以穿入4个参数,分别代表getter方法、setter方法、del方法和doc,其中doc是一个文档字符串,用于说明该属性,具体用法请见下例
例6.10
#!usr/bin/python3
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def setsize(self, size):
self.width, self.height = size
def getsize(self):
return self.width, self.height
def delsize(self):
self.width, self.height = 0, 0
size = property(getsize, setsize, delsize, "描述矩形形状")
print("doc = ", Rectangle.size.__doc__)
rect = Rectangle(2, 4)
print("size = ", rect.size) #相当于访问getsize()
rect.size = 9, 7 #相当于访问setsize()
print("rect.width = ", rect.width, " rect.height = ", rect.height)
del rect.size #相当于访问delsize()
print("rect.width = ", rect.width, " rect.height = ", rect.height)
结果:
doc = 描述矩形形状
size = (2, 4)
rect.width = 9 rect.height = 7
rect.width = 0 rect.height = 0
上面程序中用property()函数定义了一个size属性,在定义该属性时传入了4个参数,这意味着该属性可读、可写、可删除、也有说明文档,所以该程序尝试对Rectangle对象的size属性进行读、写、删除操作分别被委托给了getsize()、setsize()和delsize()来实现
6.5 继承
继承是面向对象的三大特征之一,另外两个分别是封装和多态,前面提到的私有属性和私有方法,把该隐藏的隐藏,该暴露的暴露其实就是封装,与java一样,Python 同样支持类的继承,继承可以实现代码复用,与Java不同的是,Python的继承是多继承机制,即1个子类可以同时继承多个父类
Python继承的语法需要将父类放在子类之后的括号中,如下所示:
class SubClass(SuperClass1, SuperClass2, ...)
#类定义代码
如果在定义一个Python类时没有显式指定这个类的直接父类,则这个类默认继承object类,因此object类是所有类的父类,这点和Java很相似
父类和子类的关系是一般与特殊的关系,例如水果和苹果的关系,苹果继承了水果,苹果是水果的子类,是一种特殊的水果
下面通过两个例子来感受一下python的继承
例6.11
#!usr/bin/python3
class People:
def get_name(self):
print("zhangsan")
class Student(People):
pass
student = Student()
student.get_name() #可以调用父类的get_name()方法,所以说继承可以实现代码复用
结果:
zhangsan
例6.12
#!usr/bin/python3
class Fruit:
def info(self):
print("Fruit, self.weight = ", self.weight)
class Food:
def taste(self):
print("taste good")
class Apple(Fruit, Food):
pass
apple = Apple()
apple.weight = 30
apple.info()
apple.taste()
结果:
Fruit, self.weight = 30
taste good
Python的多继承机制可能会带来一些麻烦,程序员在使用过程中要注意,如果多个父类中包含了同名的方法,此时排在前面的父类的方法会覆盖后面父类的同名方法,如下所示:
例6.13
#!usr/bin/python3
class Food:
def info(self):
print("this is food")
class Fruit:
def info(self):
print("this is fruit")
class Apple(Food, Fruit):
pass
apple = Apple()
apple.info()
结果:
this is food
6.5.1 方法重写
子类扩展了父类,子类是一种特殊的父类,大部分时候,子类总是以父类为基础,额外增加新的方法,但是有一种情况例外,子类需要重写父类的方法,子类中方法名参数列表与父类中完全一样叫做方法重写,下面程序演示了方法重写的使用方式
例6.14
#!usr/bin/python3
class people:
def get_name(self):
print("people")
class student(people):
def get_name(self):
print("student")
x = student()
x.get_name()
结果:
student
6.5.2 使用super函数调用父类的构造方法
Python的子类也会继承得到父类的构造方法,如果子类有多个直接父类,那么排在前面的父类的构造方法会被优先使用,如下所示:
例6.15
#!usr/bin/python3
class Employee:
def __init__(self, salary):
self.salary = salary
def work(self):
print("salary is ", self.salary)
class Customer:
def __inif__(self, address):
self.address = address
def info(self):
print("address is ", self.address)
class Manager(Employee, Customer):
pass
manager = Manager(10000)
manager.work() #这行会执行,因为先继承了Employee,构造函数使用的是Manager
manager.info() #这行会报错,因为address没有值
结果:
salary is 10000
Traceback (most recent call last):
File "/Users/bytedance/Desktop/code/ai-ocr/test2.py", line 24, in <module>
manager.info() #这行会报错,因为address没有值
File "/Users/bytedance/Desktop/code/ai-ocr/test2.py", line 15, in info
print("address is ", self.address)
AttributeError: 'Manager' object has no attribute 'address'
为了让子类能够同时初始化两个父类中的实例变量,子类应该定义自己的构造方法,必须重写父类的构造方法。Python要求如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。上面的例子改写后可以对两个父类构造方法都进行调用,如下所示:
例6.16
#!usr/bin/python3
class Employee:
def __init__(self, salary):
self.salary = salary
def work(self):
print("salary = ", self.salary)
class Customer:
def __init__(self, address):
self.address = address
def info(self):
print("address = ", self.address)
class Manager(Employee, Customer):
def __init__(self, salary, address):
super().__init__(salary) #调用Employee的构造方法
Customer.__init__(self, address)
manager = Manager(10000, "Beijing")
manager.work()
manager.info()
结果:
salary = 10000
address = Beijing
6.5.3 issubclass
issubclass用于判断一个类是否是另一个类的子类,如下所示:
例6.17
#!usr/bin/python3
class Animal:
def info(self):
print("Animal")
class Dog(Animal):
def info(self):
print("Dog")
print(issubclass(Dog, Animal)) #Dog 是 Animal的子类,因此此处为True
结果:
True
6.5.4 isinstance
isinstance函数可以用于检查第一个参数是否为后一个类的对象,具体用法如下:
例6.18
#!usr/bin/python3
class Dog:
def __init__(self, name):
self.name = name
dog = Dog("Tom")
if isinstance(dog, Dog):
print("dog is Dog")
结果:
dog is Dog
6.6 Python的动态性
python面向对象编程中,可以动态的为实例增加属性或删除属性,也可以增加方法
例6.19 为实例动态增加属性
#!usr/bin/python3
class Person:
hair = "black"
def __init__(self, name="zhangsan", age=30):
self.name = name
self.age = age
person = Person()
person.address = "中国" #为实例对象person增加address属性
print("person.address = ", person.address)
del person.name #删除person对象的name属性
print("person.name = ", person.name) #这行会报错
结果:
Traceback (most recent call last):
File "/Users/haha/source/builder/builder/handler/test.py", line 14, in <module>
print("person.name = ", person.name)
AttributeError: 'Person' object has no attribute 'name'
person.address = 中国
例6.20
#!usr/bin/python3
class Dog:
name = "zhangsan"
age = 20
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
return self.name + " " + str(self.age) + " eat food"
dog = Dog("Tom", 20)
dog.skills = ["swim", "jump"] #添加实例变量
print("skills = ", dog.skills)
del dog.name #删除实例变量
print("name = ", dog.name)
结果:
skills = ['swim', 'jump']
name = zhangsan
例6.21 动态增加方法
#!usr/bin/python3
class Person():
def __init__(self, name="zhangsan", age=30):
self.name = name
self.age = age
def say(self):
print("info")
person = Person()
person.info = say #动态增加方法
person.info(person)
结果:
info
如果希望为所有实例都添加方法,可以通过为类添加方法来实现,如下所示:
例6.22
#!usr/bin/python3
class Cat:
def __init__(self, name):
self.name = name
def walk_func(self):
print(self.name, "walk")
Cat.walk = walk_func
cat = Cat("Tom")
cat.walk()
结果:
Tom walk
6.7 多态
对于弱类型的语言来说,变量并没有声明类型,因此同一个变量完全可以在不同的时间引用不同的对象,当同一个变量在调用同一个方法时,完全可能呈现出多种行为,具体呈现出哪种行为由该变量所引用的对象来决定,这就是多态,具体用法如下例所示:
例6.23
#!usr/bin/python3
class Bird:
def move(self):
print("bird move")
class Dog:
def move(self):
print("dog move")
x = Bird()
x.move()
x = Dog()
x.move()
结果:
bird move
dog move
上面程序中x变量开始被赋值为Bird对象,因此当x变量执行move()方法时,它会表现为Bird类的行为,接下来x变量被赋值为Dog对象,因此当x变量执行move()方法时,它会表现出Dog的行为,这就是多态
多态是一种非常灵活的编程机制,可以根据指向的对象不同,呈现不同的行为特征,接下来的例子是通过利用多态,来实现绘制不同的图形
例6.24
#!usr/bin/python3
class Canvas:
def draw_pic(self, shape):
print("start draw")
shape.draw()
class Rectangle:
def draw(self):
print("draw Rectangle")
class Triangle:
def draw(self):
print("draw Triangle")
class Circle:
def draw(self):
print("draw Cicle")
canvas = Canvas()
canvas.draw_pic(Rectangle())
canvas.draw_pic(Triangle())
canvas.draw_pic(Circle())
结果:
start draw
draw Rectangle
start draw
draw Triangle
start draw
draw Cicle
6.11 枚举类
在某些情况下,一个类的对象是有限且固定的,比如季节类,它只有4个对象,这种实例有限且固定的类,在Python中被称为枚举类,使用方式如下
例6.25
#!usr/bin/python3
import enum
#第一个参数season是枚举类的类名,第二个参数是元组,列出所有枚举值
season = enum.Enum("season", ("Spring", "Summer", "Fall", "Winter"))
print(season.Spring)
print(season.Spring.name)
print(season.Spring.value)
结果:
season.Spring
Spring
1
最后
以上就是忧郁雨为你收集整理的第六章 python面向对象的全部内容,希望文章能够帮你解决第六章 python面向对象所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复