全部文章

16.面向对象

和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。

对象可以包含任意数量和类型的数据。

类定义

语法格式如下:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。

类对象

类对象支持两种操作:属性引用和实例化。

属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name

类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:

class MyFirstClass:
    i=1
    def fun(self):
        print("我是类MyFirstClass的fun方法")

# 实例化类(创建对象)
first_class_obj = MyFirstClass()
# 访问类的属性和方法
print(first_class_obj.i)
first_class_obj.fun()

类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:

def __init__(self): 
  self.data = []

类定义了 __init__() 方法,类的实例化操作会自动调用 __init__() 方法。如下实例化类 MyClass,对应的 __init__() 方法就会被调用:

x = MyClass()

当然, __init__() 方法可以有参数,例如:

class Human:
    def __init__(self,name,age):
        self.name = name
        self.age = age
human = Human("Tom",18)
print(human.name,human.age)

self 代表类的实例,而非类

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。

class SelfTest:
    def test(self):
        print(self)#<__main__.SelfTest object at 0x000002B86CF056A0>(self代表当前对象(实例))
        print(self.__class__)#<class '__main__.SelfTest'>(self.class 则指向类)
SelfTest().test()

从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。

self 不是 python 关键字,我们把他换成 “anyString”也是可以正常执行的:

class SelfTest:
    def test(anyString):
        print(anyString)#<__main__.SelfTest object at 0x000002B86CF056A0>
        print(anyString.__class__)#<class '__main__.SelfTest'>
SelfTest().test()

在 Python中,self 是一个惯用的名称,用于表示类的实例(对象)自身。它是一个指向实例的引用,使得类的方法能够访问和操作实例的属性。

当你定义一个类,并在类中定义方法时,第一个参数通常被命名为 self,尽管你可以使用其他名称,但强烈建议使用 self,以保持代码的一致性和可读性。

class SelfTest:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def fun(self):
        print(self.name,self.age)
#     创建一个类实例,并调用该实例的方法
SelfTest("Tom",18).fun()

在上面的例子中,self 是一个指向类实例的引用,它在 __init__ 构造函数中用于初始化实例的属性,也在 fun方法中用于访问实例的属性。通过使用 self,你可以在类的方法中访问和操作实例的属性,从而实现类的行为。

类的方法

在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。

class Human:
    # 定义基本属性
    name = ''
    age = 0
    # 定义私有属性,私有属性在类外部无法直接进行访问
    __weight = 0  # 注意!!!只有单独在前面添加两个下划线“__”,才能被私有化,如果后面也加了下划线,就无法私有化,例如“ __weight__”并不是私有化属性

    def __init__(self, name, age, weight):
        self.name = name
        self.age = age
        self.__weight = weight

    def speak(self):
        print("{}说:我{}岁了,体重{}千克".format(self.name, self.age, self.__weight))  # 类内部方法才可以访问私有属性


h = Human("Jack", 18, 55)
h.speak()  ##Jack说:我18岁了,体重55千克
print(h.name, h.age)  # Jack 18
try:
    print(h.__weight)  # AttributeError: 'Human' object has no attribute '__weight'
except AttributeError as e:
    print("私有化属性被访问啦,报错如下:\n", e)
finally:
    print("私有化属性__weight被改写为_Human__weight,等于:", h._Human__weight)  # 私有化属性__weight被改写为_Human__weight,等于: 55
'''
Python并没有真正的私有变量,而是通过名称改写(name mangling)来实现类似的效果。
上面的案例,将私有属性命名为__weight(仅前面两个下划线,后面没有),这样Python会自动将其改名为_Human__weight,从而在外部无法直接访问。
'''

类属性与方法

类的私有属性

__private_attrs双下划线开头且不以双下划线结尾,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__privateattrs

📚 私有属性的工作原理

Python 通过 Name Mangling(名称改写) 机制实现伪私有:

  1. 属性名会被自动改写为 _类名__属性名 的形式

  2. 外部直接访问原属性名时会报错

  3. 但仍可通过改写后的名称强行访问(不推荐,破坏封装性)

class Human:
    def __init__(self, name, age, weight):
        self.name = name
        self.age = age
        self.__weight = weight  # 正确的私有属性写法

    def speak(self):
        print(f"{self.name}说:我{self.age}岁了,体重{self.__weight}千克")

h = Human("Jack", 18, 55)
h.speak()
print(h.name, h.age)       # 正常访问公开属性
print(h.__weight)          # ❌ 会报错:AttributeError
print(h._Human__weight)#55    # ✅ 通过改写之后的名字强行访问(但不推荐)

⚠️ 注意事项

  • 私有属性是一种约定而非强制,主要依赖开发者自觉遵守

  • 单下划线 _weight 是另一种约定,表示 "内部使用,不要随意访问"

  • 双下划线开头且结尾的变量 __xxx__ 应保留给 Python 内置方法使用

类的方法

在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。

self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。

类的私有方法

__private_method两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods

类的私有方法实例如下:

#!/usr/bin/python3
 
class Site:
    def __init__(self, name, url):
        self.name = name       # public
        self.__url = url   # private
 
    def who(self):
        print('name  : ', self.name)
        print('url : ', self.__url)
 
    def __foo(self):          # 私有方法
        print('这是私有方法')
 
    def foo(self):            # 公共方法
        print('这是公共方法')
        self.__foo()
 
x = Site('菜鸟教程', 'www.runoob.com')
x.who()        # 正常输出
x.foo()        # 正常输出
x.__foo()      # 报错

以上实例执行结果:

类的专有方法

  • __init__ : 构造函数,在生成对象时调用
  • __del__ : 析构函数,释放对象时使用
  • __repr__ : 打印,转换
  • __setitem__ : 按照索引赋值
  • __getitem__: 按照索引获取值
  • __len__: 获得长度
  • __cmp__: 比较运算
  • __call__: 函数调用
  • __add__: 加运算
  • __sub__: 减运算
  • __mul__: 乘运算
  • __truediv__: 除运算
  • __mod__: 求余运算
  • __pow__: 乘方

运算符重载

Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:

# 运算符重载
# Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:
class Test():
    def __init__(self,a,b):
        self.a = a
        self.b = b
    #重新被转化为字符类型的方法
    def __str__(self):
        return 'Test(%d,%d)'%(self.a,self.b)
    #     重载加法运算
    def __add__(self, other):
        return Test(self.a + other.a, self.b + other.b)


test1 = Test(2, 3)
test2 = Test(3, 7)
print(test1 + test2)#如果“__str__”方法没有被重写,打印结果为:<__main__.Test object at 0x000002D08E2B1450>;重写之后打印结果:Test(5,10)

 

继承

Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。

BaseClassName(实例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:

class Human:
    # 定义基本属性
    name = ''
    age = 0
    # 定义私有属性,私有属性在类外部无法直接进行访问
    __weight = 0  # 注意!!!只有单独在前面添加两个下划线“__”,才能被私有化,如果后面也加了下划线,就无法私有化,例如“ __weight__”并不是私有化属性

    def __init__(self, name, age, weight):
        self.name = name
        self.age = age
        self.__weight = weight

    def speak(self):
        print("{}说:我{}岁了,体重{}千克".format(self.name, self.age, self.__weight))  # 类内部方法才可以访问私有属性
class student(Human):
    #定义特有属性:年级
    grade=''
    # 定义初始化方法
    def __init__(self, name, age, weight,grade):
    #调用父类初始化方法
        Human.__init__(self, name, age, weight)
        self.grade = grade
    # 重新父类speak方法
    def speak(self):
        print("我是{},今年{}岁了,目前{}在读".format(self.name,self.age,self.grade))

s = student("tom", 18, 55, "五年级")
s.speak()#我是tom,今年18岁了,目前五年级在读

多继承

Python同样有限的支持多继承形式。多继承的类定义形如下例:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

class Student(Human):
    # 定义特有属性:年级
    grade = ''

    # 定义初始化方法
    def __init__(self, name, age, weight, grade):
        # 调用父类构造函数
        Human.__init__(self, name, age, weight)
        self.grade = grade

    # 重新父类speak方法
    def speak(self):
        print("我是{},今年{}岁了,目前{}在读".format(self.name, self.age, self.grade))


# 多继承
# 定义一个演说家类
class Speaker:
    name = 'tom'
    topic = ''

    def __init__(self, name, topic):
        self.name = name
        self.topic = topic

    def speak(self):
        print("我叫{},我今天演讲的主题是:{}".format(self.name, self.topic))
#定义一个测试类继承Speaker和Student

class SomeOne(Speaker, Student):
    def __init__(self, name, age, weight, grade, topic):
        Speaker.__init__(self,name,topic)
        Student.__init__(self,name,age,weight,grade)

s = SomeOne("tom", 18, 55, "五年级", "爱护地球")
# 方法名同,默认调用的是在括号中参数位置排前父类Speaker的方法
s.speak()#我叫tom,我今天演讲的主题是:爱护地球

方法重写

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:

class Parent:#定义父类
    def fun(self):
        print("父类方法")
class Child(Parent):#定义子类
    def fun(self):
        print("子类方法")

child = Child()
child.fun()#调用子类方法
super(Child, child).fun()#调用父类方法

 

super() 函数

super() 函数是用于调用父类(超类)的一个方法。

语法

以下是 super() 方法的语法:

super(type[, object-or-type])

参数

  • type -- 类。
  • object-or-type -- 类,一般是 self

Python3.x 和 Python2.x 的一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :

Python3.x 实例:

#Python3.x 实例:
class A:
     def add(self, x):
         y = x+1
         print(y)
class B(A):
    def add(self, x):
        super().add(x)
b = B()
b.add(2)  # 3

#Python2.x 实例: class A(object): # Python2.x 记得继承 object def add(self, x): y = x+1 print(y) class B(A): def add(self, x): super(B, self).add(x) b = B() b.add(2) # 3

子类继承父类构造函数说明

如果在子类中需要父类的构造方法就需要显式地调用父类的构造方法,或者不重写父类的构造方法。

子类不重写 __init__,实例化子类时,会自动调用父类定义的 __init__。

class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('runoob')
    print ( son.getName() )

输出结果为:

name: runoob
Son runoob

如果重写了__init__ 时,实例化子类,就不会调用父类已经定义的 __init__,语法格式如下:

class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def __init__(self, name):
        print ( "hi" )
        self.name =  name
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('runoob')
    print ( son.getName() )

输出结果为:

hi
Son runoob

如果重写了__init__ 时,要继承父类的构造方法,可以使用 super 关键字:

super(子类,self).__init__(参数1,参数2,....)

还有一种经典写法:

父类名称.__init__(self,参数1,参数2,...)
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name))
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def __init__(self, name):
        super(Son, self).__init__(name)
        print ("hi")
        self.name =  name
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('runoob')
    print ( son.getName() )

 

posted @ 2025-03-14 19:24  指尖下的世界  阅读(6)  评论(0)    收藏  举报