1.面向对象,类,类的方法,类的属性,实例化,父类,子类,继承
- 面向对象:面向对象是一种编程范式,它把程序中的数据和操作数据的方法组织成一些相互关联的对象,这样可以更容易地模拟现实世界中的事物和行为。比如,你可以把一只猫看作一个对象,它有一些属性(如颜色,名字,年龄)和一些方法(如吃,睡,叫)。
- 类:类是一种定义对象的模板,它描述了对象的属性和方法。你可以把类看作是一个制造对象的工厂,它可以根据自己的设计来创建不同的对象。比如,你可以定义一个Cat类,它包含了猫的属性和方法。
- 类的方法:类的方法是一种定义在类中的函数,它可以操作类或者类的实例的属性或者其他数据。类的方法通常用self作为第一个参数,表示当前的类或者实例。比如,你可以定义一个Cat类的方法叫make_sound,它可以让猫发出叫声。
- 类的属性:类的属性是一种定义在类中的变量,它可以存储类或者类的实例的数据。类的属性分为两种:类属性和实例属性。类属性是属于整个类的属性,它可以被所有的类的实例共享。实例属性是属于每个类的实例的属性,它可以被每个实例单独修改。比如,你可以定义一个Cat类的类属性叫species,它表示猫的物种;你也可以定义一个Cat类的实例属性叫name,它表示每只猫的名字。
- 实例化:实例化是一种创建对象的过程,它根据类的定义来生成一个具体的对象,并赋予相应的属性和方法。你可以把实例化看作是从工厂中取出一个产品,并给它贴上标签。比如,你可以根据Cat类来实例化一个对象叫tommy,它是一只黑色的猫。
- 继承:继承是一种创建新类的方式,它可以让新类继承已有类的属性和方法,并添加或修改一些新的特征。你可以把继承看作是在原有产品的基础上进行改进或定制。比如,你可以定义一个新类叫Lion,它继承了Cat类,并添加了一个新属性叫mane,表示狮子是否有鬃毛。
- 父类:父类是一种已有的类,它可以被其他类继承。父类也叫做基类或超类。比如,在上面的例子中,Cat就是Lion的父类。
- 子类:子类是一种新创建的类,它继承了父类的属性和方法,并添加或修改了一些新特征。子类也叫做派生类或子级。比如,在上面的例子中,Lion就是Cat的子类。
下面是一个用python代码来展示这些概念和关系的例子:
# 定义Cat类
class Cat:
# 定义Cat类的类属性
species = "Felis catus"
# 定义Cat类的初始化方法
def __init__(self, name, color, age):
# 定义Cat类的实例属性
self.name = name
self.color = color
self.age = age
# 定义Cat类的方法
def make_sound(self):
print(f"{self.name} says: Meow!")
# 实例化Cat类
tommy = Cat("Tommy", "black", 3)
lily = Cat("Lily", "white", 2)
# 调用Cat类的属性和方法
print(tommy.species) # 输出 Felis catus
print(lily.name) # 输出 Lily
tommy.make_sound() # 输出 Tommy says: Meow!
# 定义Lion类,继承Cat类
class Lion(Cat):
# 定义Lion类的初始化方法
def __init__(self, name, color, age, mane):
# 调用父类的初始化方法
super().__init__(name, color, age)
# 定义Lion类的新属性
self.mane = mane
# 重写父类的方法
def make_sound(self):
print(f"{self.name} says: Roar!")
# 实例化Lion类
simba = Lion("Simba", "golden", 4, True)
# 调用Lion类的属性和方法
print(simba.species) # 输出 Felis catus
print(simba.mane) # 输出 True
simba.make_sound() # 输出 Simba says: Roar!
2.类中的方法和普通函数的定义和调用的异同:
- 定义的位置不同:类中的方法必须写在类的内部,而普通函数可以写在任何地方,不需要属于某个类。
- 定义的方式不同:类中的方法必须至少有一个参数,通常是self,表示当前的类或实例对象。普通函数可以没有参数,也可以有任意个参数。
- 调用的方式不同:类中的方法必须通过类名或实例对象来调用,而普通函数可以直接调用。类中的方法在调用时不需要传递self参数,而普通函数需要传递所有参数。
- 调用的效果不同:类中的方法可以访问和修改类或实例对象的属性和数据,而普通函数只能操作传入的参数或全局变量。
下面是一个简单的例子来说明这些异同:
# 定义一个普通函数
def add(x, y):
return x + y
# 定义一个类
class Calculator:
# 定义一个类属性
name = "Calculator"
# 定义一个初始化方法
def __init__(self, x, y):
# 定义两个实例属性
self.x = x
self.y = y
# 定义一个类中的方法
def add(self):
return self.x + self.y
# 调用普通函数
print(add(1, 2)) # 输出 3
# 调用类中的方法
cal = Calculator(1, 2) # 创建一个实例对象
print(cal.add()) # 输出 3
print(Calculator.add(cal)) # 输出 3
3.类中的方法的第一个参数可以不是self吗
这个过程是由python解释器自动完成的,你不需要显式地写出第一个参数。但是你需要在定义方法时指定第一个参数的名字,这个名字就是self。当然,你也可以使用其他的名字,但是这样做会违反python的编码风格和习惯。
4.定义一个类的方法时,第一个参数永远是self,调用时不需要传self
当你通过实例对象来调用类中的方法时,实例对象的引用就会自动作为第一个参数传递给方法,这个参数就是self。self表示当前的类或实例对象,它可以让方法访问和操作对象的属性和数据。
你可以把self看作是一个变量,它的值就是实例对象的引用。当你定义一个类中的方法时,你需要在参数列表中指定self,但是当你调用这个方法时,你不需要传递self,因为python解释器会自动把实例对象的引用赋值给self。
下面是一个简单的例子来说明这个过程:
# 定义一个类
class Person:
# 定义一个初始化方法
def __init__(self, name, age):
# 定义两个实例属性
self.name = name
self.age = age
# 定义一个类中的方法
def say_hello(self):
print(f"Hello, I am {self.name}, and I am {self.age} years old.")
# 创建一个实例对象
p = Person("Alice", 20)
# 调用类中的方法
p.say_hello() # 输出 Hello, I am Alice, and I am 20 years old.
# 等价于
Person.say_hello(p) # 输出 Hello, I am Alice, and I am 20 years old.
5.在类中,哪些参数,方法或者属性需要放在“ def__init__(self)”里面?
- 参数:init 方法的第一个参数必须是self,它表示当前的类或实例对象。除此之外,init 方法可以有任意个参数,它们可以用来初始化对象的属性或者执行其他操作。当你创建一个类的实例对象时,你需要传递相应的参数给__init__ 方法,除了self之外。比如,如果你定义了一个Person类,它的__init__ 方法有三个参数:self, name, age。那么当你创建一个Person类的实例对象时,你需要传递两个参数给__init__ 方法:name, age。例如:p = Person(“Alice”, 20)。
- 方法:init 方法本身就是一个类中的方法,它用来初始化对象的属性或者执行其他操作。除了__init__ 方法之外,类中还可以有其他的方法,它们可以用来定义对象的行为或者功能。这些方法不需要放在__init__ 方法里面,而是放在类的外面,和__init__ 方法平级。比如,如果你定义了一个Person类,它有一个say_hello方法,它用来打印对象的名字和年龄。那么这个方法不需要放在__init__ 方法里面,而是放在类的外面,和__init__ 方法平级。例如:
# 定义一个Person类
class Person:
# 定义一个初始化方法
def __init__(self, name, age):
# 定义两个实例属性
self.name = name
self.age = age
# 定义一个say_hello方法
def say_hello(self):
print(f"Hello, I am {self.name}, and I am {self.age} years old.")
-
6.属性分为两种:类属性和实例属性。
- 属性:属性是一种定义在类中的变量,它可以存储类或者类的实例的数据。属性分为两种:类属性和实例属性。类属性是属于整个类的属性,它可以被所有的类的实例共享。实例属性是属于每个类的实例的属性,它可以被每个实例单独修改。一般来说,类属性不需要放在__init__ 方法里面,而是放在类的外面,和方法平级。实例属性需要放在__init__ 方法里面,并且通过self参数来赋值。比如,如果你定义了一个Person类,它有一个类属性叫species,它表示人类的物种;它也有两个实例属性叫name和age,它们表示每个人的名字和年龄。那么这些属性应该这样定义:
# 定义一个Person类
class Person:
# 定义一个类属性
species = "Homo sapiens"
# 定义一个初始化方法
def __init__(self, name, age):
# 定义两个实例属性
self.name = name
self.age = age
# 定义一个通过实例对象来调用的方法
def say_hello(self):
print(f"Hello, I am {self.name}, and I belong to {self.species}.")
# 定义一个通过类名来调用的方法
@classmethod
def say_species(cls):
print(f"We are {cls.species}.")
# 创建两个Person类的实例对象
p1 = Person("Alice", 20)
p2 = Person("Bob", 25)
# 调用两个通过实例对象来调用的方法
p1.say_hello() # 输出 Hello, I am Alice, and I belong to Homo sapiens.
p2.say_hello() # 输出 Hello, I am Bob, and I belong to Homo sapiens.
# 调用两个通过类名来调用的方法
Person.say_species() # 输出 We are Homo sapiens.
7.类中一定要有“def __init__”这个方法吗
类中一定要有“def init”这个方法吗?答案是不一定。init 方法是一种特殊的方法,它用来初始化对象的属性或者执行其他操作。如果你的类不需要做任何初始化的工作,或者你想用其他的方法来做初始化的工作,那么你可以不定义__init__ 方法。
但是,如果你不定义__init__ 方法,那么python解释器会自动为你的类提供一个默认的__init__ 方法,它什么也不做,只是接受self参数,并且返回一个空的对象。 这样,你就不能给你的对象赋予任何属性或者数据,也不能执行任何操作。这可能会导致你的对象缺乏功能或者意义。
因此,一般来说,推荐你为你的类定义一个__init__ 方法,除非你有特殊的理由不这样做。这样,你可以根据你的类的设计和需求来初始化你的对象的属性或者数据,或者执行其他操作。这样,你可以让你的对象更有功能和意义。
你想看一个对比的例子,来说明有和没有__init__ 方法的区别吗?好的,我可以给你一个例子,但是我要提醒你,有__init__ 方法的类通常是一个更好的编程习惯,因为它可以让你的对象有初始化的功能和意义。如果你想创建一个没有功能和意义的对象,你可以不定义__init__ 方法,或者使用其他的方法来初始化你的对象。
下面是一个对比的例子,来说明有和没有__init__ 方法的区别:
# 定义一个没有__init__ 方法的类
class Rectangle1:
# 定义一个计算面积的方法
def area(self):
return self.length * self.width
# 定义一个计算周长的方法
def perimeter(self):
return 2 * (self.length + self.width)
# 定义一个有__init__ 方法的类
class Rectangle2:
# 定义一个初始化方法
def __init__(self, length, width):
# 定义两个实例属性
self.length = length
self.width = width
# 定义一个计算面积的方法
def area(self):
return self.length * self.width
# 定义一个计算周长的方法
def perimeter(self):
return 2 * (self.length + self.width)
# 创建两个没有__init__ 方法的类的实例对象
r1 = Rectangle1()
r2 = Rectangle1()
# 创建两个有__init__ 方法的类的实例对象
r3 = Rectangle2(10, 5)
r4 = Rectangle2(15, 8)
# 给两个没有__init__ 方法的类的实例对象赋予属性
r1.length = 10
r1.width = 5
r2.length = 15
r2.width = 8
# 调用两个没有__init__ 方法的类的实例对象的方法
print(r1.area()) # 输出 50
print(r1.perimeter()) # 输出 30
print(r2.area()) # 输出 120
print(r2.perimeter()) # 输出 46
# 调用两个有__init__ 方法的类的实例对象的方法
print(r3.area()) # 输出 50
print(r3.perimeter()) # 输出 30
print(r4.area()) # 输出 120
print(r4.perimeter()) # 输出 46
你可以看到,在这个例子中:
- Rectangle1是一个没有__init__ 方法的类,它只有两个方法:area和perimeter,它们分别用来计算矩形的面积和周长。
- Rectangle2是一个有__init__ 方法的类,它也有两个方法:area和perimeter,它们分别用来计算矩形的面积和周长。但是它还有一个初始化方法,它用来给对象赋予属性length和width,它们分别表示矩形的长度和宽度。
- r1和r2是两个没有__init__ 方法的类的实例对象,它们没有任何属性或者数据。我们需要给它们赋予属性length和width,才能调用它们的方法area和perimeter。
- r3和r4是两个有__init__ 方法的类的实例对象,它们已经有了属性length和width,并且根据传入的参数来初始化这些属性。我们不需要再给它们赋予属性length和width,就可以直接调用它们的方法area和perimeter。
这个例子可以看出,有和没有__init__ 方法的区别主要在于:
- 是否需要给对象赋予属性或者数据:有__init__ 方法的类可以在创建对象时就给对象赋予属性或者数据,而没有__init__ 方法的类需要在创建对象后才能给对象赋予属性或者数据。
- 是否能保证对象有正确的属性或者数据:有__init__ 方法的类可以保证对象有正确的属性或者数据,因为它们是在初始化方法中赋值的,而没有__init__ 方法的类不能保证对象有正确的属性或者数据,因为它们是在外部赋值的,可能会出现错误或者缺失。
- 是否能让对象有更多的功能或者意义:有__init__ 方法的类可以让对象有更多的功能或者意义,因为它们可以根据类的设计和需求来初始化对象的属性或者数据,或者执行其他操作,而没有__init__ 方法的类只能让对象有最基本的功能或者意义,因为它们只能根据默认的方式来创建空的对象。
8.类中私有或者公有变量,属性,方法的定义和调用方式
- 我们可以通过在变量,属性,方法的名字前加上双下划线(__)来表示它们是私有的。这样,它们就不能被外部访问或修改,只能在类内部使用。这是一种实现数据封装和信息隐藏的技巧。
- 但是,这种技巧并不完美,因为python会对双下划线开头的变量,属性,方法进行一种叫做名称修饰(name mangling)的处理。它会把这些变量,属性,方法的名字改成_类名__变量名的形式。这样,我们就可以通过这种形式来访问或修改私有成员。比如,我们可以通过a1._Animal__id来访问或修改a1对象的私有属性__id。
# 定义一个类
class Animal:
# 定义一个公有类属性
kingdom = "Animalia"
# 定义一个私有类属性
__count = 0
# 定义一个公有初始化方法
def __init__(self, name):
# 定义一个公有实例属性
self.name = name
# 定义一个私有实例属性
self.__id = Animal.__count
# 调用一个私有类方法
Animal.__increase_count()
# 定义一个公有实例方法
def introduce(self):
print(f"Hi, I am {self.name}, and I belong to {self.kingdom}.")
# 调用一个私有实例方法
self.__show_id() #可以先调用后定义
# 定义一个私有实例方法
def __show_id(self):
print(f"My ID is {self.__id}.")
# 定义一个公有类方法
@classmethod
def show_kingdom(cls):
print(f"We are {cls.kingdom}.")
# 调用一个私有类方法
cls.__show_count()
# 定义一个私有类方法
@classmethod
def __show_count(cls):
print(f"There are {cls.__count} animals.")
# 定义一个私有类方法
@classmethod
def __increase_count(cls):
cls.__count += 1
# 创建两个Animal类的实例对象
a1 = Animal("Cat")
a2 = Animal("Dog")
# 调用两个公有实例方法
a1.introduce()
# 输出 Hi, I am Cat, and I belong to Animalia.
# 输出 My ID is 0.
a2.introduce()
# 输出 Hi, I am Dog, and I belong to Animalia.
# 输出 My ID is 1.
# 调用两个公有类方法
Animal.show_kingdom()
# 输出 We are Animalia.
# 输出 There are 2 animals.
# 尝试访问或修改两个公有实例属性
print(a1.name) # 输出 Cat
print(a2.name) # 输出 Dog
a1.name = "Lion" # 修改成功
a2.name = "Wolf" # 修改成功
# 尝试访问或修改两个私有实例属性
try:
print(a1.__id) # 报错 AttributeError: 'Animal' object has no attribute '__id'
except AttributeError as e:
print(e)
try:
print(a2.__id) # 报错 AttributeError: 'Animal' object has no attribute '__id'
except AttributeError as e:
print(e)
try:
a1.__id = 100 # 没有报错,但是没有修改成功,而是创建了一个新的公有属性__id
except Exception as e:
print(e)
try:
a2.__id = 200 # 没有报错,但是没有修改成功,而是创建了一个新的公有属性__id
except Exception as e:
print(e)
# 尝试访问或修改两个公有类属性
print(Animal.kingdom) # 输出 Animalia
Animal.kingdom = "Mammalia" # 修改成功
# 尝试访问或修改两个私有类属性
try:
print(Animal.__count) # 报错 AttributeError: type object 'Animal' has no attribute '__count'
except AttributeError as e:
print(e)
try:
Animal.__count = 100 # 没有报错,但是没有修改成功,而是创建了一个新的公有属性__count
except Exception as e:
print(e)
# 尝试访问或修改两个私有实例方法
try:
a1.__show_id() # 报错 AttributeError: 'Animal' object has no attribute '__show_id'
except AttributeError as e:
print(e)
try:
a2.__show_id() # 报错 AttributeError: 'Animal' object has no attribute '__show_id'
except AttributeError as e:
print(e)
# 尝试访问或修改两个私有类方法
try:
Animal.__show_count() # 报错 AttributeError: type object 'Animal' has no attribute '__show_count'
except AttributeError as e:
print(e)
try:
Animal.__increase_count() # 报错 AttributeError: type object 'Animal' has no attribute '__increase_count'
except AttributeError as e:
print(e)
9.类中方法调用顺序及实例方法,类方法,和静态方法 的区别和使用场景
- 实例方法在类中调用,可以先调用后定义,但是要注意以下几点:
- 实例方法只能在其他实例方法中调用,不能在类方法或者静态方法中调用。
- 实例方法只能通过self参数来调用,不能通过类名或者实例对象来调用。
- 实例方法必须在创建类的实例对象之前定义,否则会报错AttributeError: ‘ClassName’ object has no attribute ‘method_name’。
- 类方法在类中调用,可以先调用后定义,但是要注意以下几点:
- 类方法可以在其他类方法或者静态方法中调用,但是不能在实例方法中调用。
- 类方法可以通过cls参数或者类名来调用,但是不能通过实例对象来调用。
- 类方法必须在创建类的实例对象之前定义,并且要使用@classmethod装饰器来标识,否则会报错TypeError: method_name() takes no arguments (1 given)。
- 静态方法在类中调用,可以先调用后定义,但是要注意以下几点:
- 静态方法可以在其他静态方法中调用,但是不能在实例方法或者类方法中调用。
- 静态方法可以通过类名来调用,但是不能通过self参数或者实例对象来调用。
- 静态方法必须在创建类的实例对象之前定义,并且要使用@staticmethod装饰器来标识,否则会报错TypeError: method_name() takes no arguments (1 given)。
实例方法,类方法,和静态方法的区别主要在于以下几个方面:
- 定义方式:实例方法是一个普通的函数,类方法和静态方法都是通过函数装饰器的方式实现的;实例方法需要传入self参数,类方法需要传入cls参数,静态方法无需传入self参数或者是cls参数(但不等同于不能传入参数)。
- 调用方式:实例方法只能被类的实例对象调用,类方法和静态方法可以被类或类的实例对象调用;实例方法只能通过self参数来调用其他实例属性或方法,类方法可以通过cls参数或者类名来调用其他类属性或方法,静态方法可以通过类名来调用其他静态方法。
- 访问权限:实例方法可以访问类中的所有属性和方法以及实例中的属性和方法,而类方法和静态方法只能访问类中的属性和方法。
- 应用场景:实例方法通常用来处理与具体实例相关的逻辑,比如初始化属性,修改状态,显示信息等;类方法通常用来处理与整个类相关的逻辑,比如创建实例,修改类属性,显示类信息等;静态方法通常用来处理与类无关的逻辑,比如工具函数,辅助函数等。
下面是一个简单的例子来说明这些区别:
# 定义一个Person类
class Person:
# 定义一个公有类属性
species = "Homo sapiens"
# 定义一个私有类属性
__population = 0
# 定义一个公有初始化方法
def __init__(self, name, age):
# 定义两个公有实例属性
self.name = name
self.age = age
# 定义一个私有实例属性
self.__id = Person.__population
# 调用一个私有类方法
Person.__increase_population()
# 定义一个公有实例方法
def say_hello(self):
print(f"Hello, I am {self.name}, and I belong to {self.species}.")
# 调用一个私有实例方法
self.__show_id()
# 定义一个私有实例方法
def __show_id(self):
print(f"My ID is {self.__id}.")
# 定义一个公有类方法
@classmethod
def say_species(cls):
print(f"We are {cls.species}.")
# 调用一个私有类方法
cls.__show_population()
# 定义一个私有类方法
@classmethod
def __show_population(cls):
print(f"Our population is {cls.__population}.")
# 定义一个私有类方法
@classmethod
def __increase_population(cls):
cls.__population += 1
# 定义一个公有静态方法
@staticmethod
def is_adult(age):
return age >= 18
# 创建两个Person类的实例对象
p1 = Person("Alice", 20)
p2 = Person("Bob", 25)
# 调用两个公有实例方法
p1.say_hello()
# 输出 Hello, I am Alice, and I belong to Homo sapiens.
# 输出 My ID is 0.
p2.say_hello()
# 输出 Hello, I am Bob, and I belong to Homo sapiens.
# 输出 My ID is 1.
# 调用两个公有类方法
Person.say_species()
# 输出 We are Homo sapiens.
# 输出 Our population is 2.
# 调用一个公有静态方法
print(Person.is_adult(16)) # 输出 False
print(Person.is_adult(21)) # 输出 True