Python Object-Oriented Programming and Decorators: Classes, Inheritance, property, and Closures
类 从这里开始,我们将进入一个在C语言没有涉及的思想 面向对象编程 ,在前面的使用中我们的程序设计是面向过程的;面向对象编程是最有效的软件编写方法之一 。
我们先编写现实世界中事务和情景的类 class 然后基于这些类来创建对象 object 这样一大类对象自动具备这些类的通用行为 这称为 继承 inheritance 我们隐藏了类的内部细节只给了一些调用的函数 这叫做 封装 encapsulation 然后赋予对象属于自己的个性 称为 类的特化(specialization)和泛化(generalization) 通过多态(polymorphism) 实现基于对象类型的动态分派。 最后我们程序的逼真程度会达到一个高的可怕的程度
面向对象的编程思想不仅仅是为了拟真 编程就是程序员按照计算机的工作方式控制计算机完成各种任务 当我们需要开发一个复杂的系统时,代码的复杂性会让开发和维护工作都变得举步维艰 面向对象的编程思想让我们能够以更加自然的方法来处理这些问题 而封装的存在降低了开发人员的压力
简单的说,类是对象的蓝图和模板,而对象是类的实例。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。
类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。
创建和使用类 定义类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Dog : def __init__ (self,name,age ): self .name = name self .age = age def sit (self ): print (f"{self.name} is now sitting" ) def roll_over (self ): print (f"{self.name} is rolled over" )
创建实例 1 2 3 4 5 6 7 8 9 10 11 12 13 my_dog = Dog('Whllie' ,6 ) print (my_dog.name)print (my_dog.age)my_dog.name my_dog.sit() my_dog0 = Dog('Whllie' ,6 ) my_dog1 = Dog('Whllie' ,6 ) my_dog2 = Dog('Whllie' ,7 )
私有化问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Test : def __init__ (self, foo ): self .__foo = foo def __bar (self ): print (self .__foo) print ('__bar' ) def main (): test = Test('hello' ) test.__bar() print (test.__foo) if __name__ == "__main__" : main()
以上代码无法正常的运行,因为定义的方法与属性都添加了双下划线,是私有的,无法直接从外部调用,只可以通过公开的方法间接调用,比如
1 2 3 def access_private (self ): self .__bar()
但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们
在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的 ,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻
使用类和实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Car : def __init__ (self,make,model=tesla,year ): self .make = make self .model = model self .year = year self .odometer = 0 def get_descriptive_name (self ): long_name = f"{self.year} {self.make} {self.model} " return long_name def read_odometer (self ): print (f"This car has {self.odometer} miles on it" ) def update_odometer (self,mileage ): self .odometer = mileage def add_odometer (self,mile ): if mile >= 0 : self .odometer += mileage else : print ("Error!" ) my_car = Car('audi' ,'a4' ,2022 ) print (my_car.get_descriptive_name())my_car.read_odometer() my_car.odometer = 100 my_car.read_odometer() my_car.update_odometer(200 ) my_car.read_odometer() my_car.add_odometer(300 ) my_car.read_odometer() my_car.add_odometer(-100 )
property装饰器 不要把属性直接保护 但是还要能够看到或者修改属性 这就是property装饰器的意义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Person (object ): def __init__ (self, name, age ): self ._name = name self ._age = age @property def name (self ): return self ._name @property def age (self ): return self ._age @age.setter def age (self, age ): self ._age = age def play (self ): if self ._age <= 16 : print ('%s正在玩飞行棋.' % self ._name) else : print ('%s正在玩斗地主.' % self ._name) def main (): person = Person('王大锤' , 12 ) person.play() person.age = 22 person.play() if __name__ == '__main__' : main()
slots魔法 Python是一门动态语言 。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。
但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Person (object ): __slots__ = ('_name' , '_age' , '_gender' ) def __init__ (self, name, age ): self ._name = name self ._age = age @property def name (self ): return self ._name @property def age (self ): return self ._age @age.setter def age (self, age ): self ._age = age def play (self ): if self ._age <= 16 : print ('%s正在玩飞行棋.' % self ._name) else : print ('%s正在玩斗地主.' % self ._name) def main (): person = Person('王大锤' , 22 ) person.play() person._gender = '男'
静态方法和类方法 之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息.
实际上,我们写在类中的方法并不需要都是对象方法。有的方法需要在创建对象(不知道能不能创建成功)的时候就被调用,我们可以使用静态方法来解决这类问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from math import sqrtclass Triangle (object ): def __init__ (self, a, b, c ): self ._a = a self ._b = b self ._c = c @staticmethod def is_valid (a, b, c ): return a + b > c and b + c > a and a + c > b def perimeter (self ): return self ._a + self ._b + self ._c def area (self ): half = self .perimeter() / 2 return sqrt(half * (half - self ._a) * (half - self ._b) * (half - self ._c)) def main (): a, b, c = 3 , 4 , 5 if Triangle.is_valid(a, b, c): t = Triangle(a, b, c) print (t.perimeter()) print (t.area()) else : print ('无法构成三角形.' ) if __name__ == '__main__' : main()
Python还可以在类中定义类方法。他依赖与类本身而无须实例,用于处理一些不涉及实例的类工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from time import time, localtime, sleepclass Clock (object ): """数字时钟""" def __init__ (self, hour=0 , minute=0 , second=0 ): self ._hour = hour self ._minute = minute self ._second = second @classmethod def now (cls ): ctime = localtime(time()) return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec) def run (self ): """走字""" self ._second += 1 if self ._second == 60 : self ._second = 0 self ._minute += 1 if self ._minute == 60 : self ._minute = 0 self ._hour += 1 if self ._hour == 24 : self ._hour = 0 def show (self ): """显示时间""" return '%02d:%02d:%02d' % \ (self ._hour, self ._minute, self ._second) def main (): clock = Clock.now() while True : print (clock.show()) sleep(1 ) clock.run() if __name__ == '__main__' : main()
类之间的关系 简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。
is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。
继承与多态 编写一个新的类不一定要从空白开始 我们前面就是从空白开始 现在我们来看看一个现成类的特殊版本是怎么继承的
1 2 3 4 5 6 7 8 9 10 11 class EletricCar (Car ): def __init__ (self,make,model,year ): super ().__init__(make,model,year) self .battery_size = 60 def describe_battery (self ): print (self .battery.size) def fill_gas_tank (self ): print ("No gas tank" )
面向对象的核心是对自然界存在的事务的定义 事实上非常多的事务都很复杂 如果在一个类里面定义太多的属性会让人感到崩溃并且难以使用 我们有时候会在类里面放一个类 比如电动汽车类里面的电池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Battery : def __init__ (self,battery_size=75 ): self .battery_size = battery_size def decribe_battery (self ): print (f"size is self.battery_size" ) class EletricCar (Car ): def __init__ (self,make,model,year ): super ().__init__(make,model,year) self .battery = Battery() self .battery.describe_battery()
导入类 正如导入函数一样 有时候我们需要把类存储到一起 在需要的时候再导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from car import Carfrom car import Car,ElectricCarimport carcar.Car('tesla' ,'model 3' ,2019 ) from car import *from car import ElectricCar as EC
装饰器(decorator) 什么是装饰器 Python 装饰器是一种强大且灵活的工具,它允许你在不修改原函数代码的情况下,对函数或类的功能进行扩展。
我们前面在类中讨论过的 @property 与 @xxx.setter 装饰器都是Python提前设定好的装饰器,可以用于保护属性与修改属性。@staticmethod装饰器与@classmethod用于创建静态方法与类方法,他们都是Python中提前编写好的可以直接使用的装饰器,功能也介绍清楚了。
装饰器本质上是一个可调用对象(通常是函数),它接收一个函数或类作为输入,并返回一个新的函数或类(一个高阶的函数 )。新的函数或类通常会在原函数或类的基础上添加额外的功能。前面介绍的几个decorator都起到了这个效果。
装饰器使用 @ 符号来应用到函数或类上,其基本语法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def decorator (func ): def wrapper (*args, **kwargs ): result = func(*args, **kwargs) return result return wrapper @decorator def my_function (): pass my_function = decorator(my_function)
当使用 @decorator 语法将装饰器应用到函数上时,Python 会自动将被装饰的函数作为参数传递给装饰器函数,并将装饰器函数返回的新函数赋值给原函数名。因此,调用被装饰的函数实际上是调用装饰器返回的新函数。
自定义装饰器与效果 用于普通函数的装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import timedef timer_decorator (func ): def wrapper (*args, **kwargs ): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print (f"函数 {func.__name__} 执行时间: {end_time - start_time} 秒" ) return result return wrapper @timer_decorator def add (a, b ): return a + b result = add(3 , 5 ) print (result)
装饰器也可以用于类的方法,本质上与普通函数没什么区别,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import timedef timer_decorator (func ): def wrapper (self, *args, **kwargs ): start_time = time.time() result = func(self , *args, **kwargs) end_time = time.time() print (f"方法 {func.__name__} 执行时间: {end_time - start_time} 秒" ) return result return wrapper class MyClass : @timer_decorator def my_method (self, a, b ): return a + b obj = MyClass() result = obj.my_method(3 , 5 ) print (result)
进一步的,用于静态方法与类方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import timedef timer_decorator (func ): def wrapper (*args, **kwargs ): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print (f"方法 {func.__name__} 执行时间: {end_time - start_time} 秒" ) return result return wrapper class MyClass : @staticmethod @timer_decorator def static_method (a, b ): return a + b @classmethod @timer_decorator def class_method (cls, a, b ): return a + b result1 = MyClass.static_method(3 , 5 ) print (result1)result2 = MyClass.class_method(3 , 5 ) print (result2)
甚至可以用于类,当然这里语法稍微变化了一些,只需要接受一个类即可
1 2 3 4 5 6 7 8 9 10 def add_attribute (cls ): cls.new_attribute = "这是新添加的属性" return cls @add_attribute class MyClass : pass obj = MyClass() print (obj.new_attribute)