类与对象
数据类自动生成常用的方法如__init__、__repr__等,减少样板代码,让开发者专注于业务逻辑。
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
面向对象编程
面向对象编程——Object Oriented Programming,简称 OOP,是一种程序设计思想。OOP 把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
在 Python 中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。
Class 是一种抽象概念,比如我们定义的 Class——印花T恤,是指印花T恤这个概念,而实例(Instance)则是一个个具体的印花T恤,比如,我手上的向日葵印花T恤和玫瑰印花T恤是两个具体的印花T恤。
所以,面向对象的设计思想是抽象出 Class,根据 Class 创建 Instance。
面向对象的抽象程度又比函数要高,因为一个 Class 既包含数据,又包含操作数据的方法。
class
python通过class关键字定义一个类,类如果不继承其他类,则默认继承object类,此时可以省略括号与object。
# class ClassName(object):
class ClassName:
"""class docstring"""
pass
instance = ClassName()
如果在定义时,括号内含有一个或多个其他类,那么这个类就继承了这些类?。
类继承的基本形式:
# class ClassName(ParentClass1, ParentClass2, ...):
class ClassName(ParentClass): # ParentClass 就是用来继承的父类
"""class docstring"""
# 类里的变量称为属性。
name = "ClassName"
# 类里的函数称为方法。
def method(self):
return
# 类被调用称为创建实例或实例化
# instance称之为ClassName类的实例。
instance = ClassName()
方法和属性
方法按照是否可访问实例和类的属性分为:
- 类方法:
@classmethod装饰器+第一个参数传入类(约定为cls) ,类或实例可以调用,能访问类的属性和方法。 - 静态方法:
@staticmethod装饰器,类或实例可以调用,不能访问实例和类和实例的属性和方法。 - 实例方法:约定第一个参数传入实例(约定为self),约定实例化后调用,可以访问实例的属性和方法。
不遵守self和cls命名约定不会让你代码报错,但是遵守约定可以让你的代码可读性更高。
class ClassName:
def instance_method(is_not_self):
print("instance_method", is_not_self)
# 不遵守第一个参数传入实例的约定,可通过类调用。但是不能实例化后调用。
# 这种方法如果加上@staticmethod装饰器,既可以类调用,也可以实例化后调用。
def instance_method_no_self():
print("instance_method_no_self")
# 如果你通过类调用方法,那么它就是普通函数。如果有参数需要手动传入参数。
ClassName.instance_method_no_self() # instance_method_no_self
ClassName.instance_method(1) # instance_method 1
ClassName.instance_method(ClassName())# instance_method <__main__.ClassName object at 0x0000021973262280>
# 如果实例化后调用,那么它会自动将实例作为第一个参数传入。如果方法没有参数则会报错。
ClassName().instance_method() # instance_method <__main__.ClassName object at 0x0000021973262280>
ClassName().instance_method_no_self() # TypeError: ClassName.instance_method_no_self() takes 0 positional arguments but 1 was given
方法和属性按照是否以下划线_开头和结尾,可以分为以下几种:
- 以 __ 开头和结尾的方法和属性,大多有特殊用途。称呼多样,称为 “special/双下划线/魔法” 方法和属性。
- 不以 __ 开头和结尾的方法和属性,即共有的方法和属性。
- 以 __ 开头不以 __ 结尾的方法,名称修饰方法,解释器会自动将其名称修改。
- 以 _ 开头,私有方法,不过不是真正私有,而是可以调用的,但是不会被代码自动完成所记录(即 Tab 键之后不会显示)
class MyDemoClass(object):
# special 方法(有特殊用途)
def __init__(self):
print("special.")
# 共有方法
def get_value(self):
print("get_value is public method.")
# 私有方法:会被代码自动完成所记录
def _get_name(self):
print("_get_name is private method.")
# 名称修饰方法
# 当你定义一个 __my_method() 方法时,Python 解释器会自动将其名称修改为
# _ClassName__my_method() 的形式,以避免在子类中发生名称冲突
def __get_type(self):
print("__get_type is really special method.")
demo = MyDemoClass() # special.
demo.get_value() # get_value is public method.
demo._get_name() # _get_name is private method
demo._MyDemoClass__get_type() # __get_type is really special method.
继承
继承可以让子类拥有父类的所有方法和属性,子类可以增加新方法或者重写父类的方法。
class Clothes(object):
def __init__(self, color="green"):
self.color = color
def out_print(self):
return self.__class__.__name__, self.color
my_clothes = Clothes()
my_clothes.color
my_clothes.out_print()
# 定义一个子类,继承父类的所有方法
class NikeClothes(Clothes):
def change_color(self):
if self.color == "green":
self.color = "red"
# 子类继承父类的所有方法
your_clothes = NikeClothes()
your_clothes.color
your_clothes.out_print()
# 同时子类也有自己的方法可用
your_clothes.change_color()
your_clothes.color
# 如果想对父类的方法进行修改,只需要在子类中重定义这个方法即可
class AdidasClothes(Clothes):
def change_color(self):
if self.color == "green":
self.color = "black"
def out_print(self):
self.change_color()
return self.__class__.__name__, self.color
him_clothes = AdidasClothes()
print(him_clothes.color)
him_clothes.change_color()
print(him_clothes.color)
print(him_clothes.out_print())
异常是标准库中的类,这意味着我们可以自定义异常类:
尝试在文本输入框输入:k,start,q
class CommandError(ValueError):
print("bad command operation. must input 'start', 'stop', 'pause'")
valid_commands = {'start', 'stop', 'pause'}
while True:
command = input('>')
if command == 'q':
break
try:
if command.lower() not in valid_commands:
raise CommandError('Invalid command: %s' % command)
print('input command:', command)
except CommandError:
print("bad command string: %s" % command)
super
super(class, instance) 函数用于在子类中调用父类(或超类)的方法,特别是支持多继承的场景下,它会根据方法的解析顺序Method Resolution Order, MRO来决定调用查找类的下一个类的方法。
class决定从MRO的哪个类之后开始查找(即从该类的下一个类开始找),参数instance决定使用谁的MRO。instance可以是实例对象或类对象。
无参数形式
无参数写法必须在方法或实例方法内部使用,即有self或cls上下文。当在类的方法内部使用 super() 时,它会自动推断当前类和当前上下文。
将当前类传入第一个参数,将当前方法的第一个实例或类(即self或cls)传入第二个参数。
class Child:
def self_method(self):
# 自动推断当前类和当前上下文,将当前类传入第一个参数,将当前上下文传入第二个参数。
print(id(super())==id(super(Child, self))) # True
@classmethod
def class_method(cls):
# 自动推断当前类和当前上下文,将当前类传入第一个参数,将当前上下文传入第二个参数。
print(id(super())==id(super(Child, cls))) # True
@staticmethod
def static_method():
# 静态方法没有上下文,不传入第一个参数,不传入第二个参数。
print(id(super()))
child = Child()
child.self_method() # True
child.class_method() # True
child.static_method() # super(): no arguments
使用super().指定方法()时,会将当前的上下文(self 或 cls)作为打包为super对象,传递给"当前的上下文(self 或 cls)对应MRO的下一个类的指定方法",然后调用。
- 如果指定方法是静态方法,则上下文不作为参数,因为静态方法本身就不依赖于实例或类来执行。
- 如果指定方法是类方法,则不论上下文是实例对象还是类对象。类方法都能解析为类对象正常执行。
- 如果指定方法是实例方法,则传递当前的上下文如果是不是实例对象,而是其他(如类对象)会报错。
class Parent:
def self_method(self):
print("Parent self_method: ",self)
@classmethod
def class_method(cls):
# cls参数永远指向调用该方法的类
# 不论提供的是类还是实例,最终都会调用类的方法。
print("Parent class method:",cls)
@staticmethod
def static_method():
print("Parent static method")
class Child(Parent):
def self_method(self):
# 将当前的self作为参数,传递给"self即Child,对应的MRO的Child的下一个类的方法",然后调用
super().self_method() # 调用父类的 self_method 方法
super().class_method() # 调用父类的 class_method 方法
super().static_method() # 调用父类的 static_method 方法
@classmethod
def class_method(cls):
# 将当前的cls作为参数,传递给"cls即Child,对应的MRO的Child的下一个类的方法",然后调用
# super().self_method() # 调用父类的 self_method 方法
super().class_method() # 调用父类的 class_method 方法
super().static_method() # 调用父类的 static_method 方法
child = Child()
child.self_method()
"""
Parent self_method: <__main__.Child object at 0x000001CAE6FBC1D0>
Parent class method: <class '__main__.Child'>
Parent static method
"""
child.class_method()
"""
TypeError: Parent.self_method() missing 1 required positional argument: 'self'
Parent class method: <class '__main__.Child'>
Parent static method
"""