5、面向对象编程

一个类(Class)能够创建一种新的类型(Type),其中对象(Object)就是类的实例(Instance)。可以这样来类比:你可以拥有类型 int 的变量,也就是说存储整数的变量是 int 类的实例(对象)。(即使是整数也会被视为对象( int 类的对象)。这不同于 C++ 与 Java(1.5版之前),在它们那儿整数是原始内置类型。

1、从属于对象或类的变量叫作字段,字段有两种类型——它们属于某一类的各个实例或对象(实例变量)或是从属于某一类本身(类变量

2、对象还可以使用属于类的函数来实现某些功能,这种函数叫作类的方法

1、类的创建

我们通过使用 class 语句与这个类的名称来创建一个新类,创建实例是通过类名+()实现的:

class Person:
    pass # 一个空的代码块
p = Person()
print(p)

输出:

<__main__.Person object at 0x0000017ECD49E940>

后面的0x0000017ECD49E940是内存地址,每个object的地址都不一样。

2、类方法

类方法与普通函数只有一种特定的区别——前者必须多加一个参数在参数列表开头,这个名字必须添加到参数列表的开头,按照惯例,该参数使用self 这一名称,如果有一个没有参数的方法,依旧必须拥有一个参数—— self,self这种特定的变量引用的是对象本身(self代表类的实例,而非类)。

假设你有一个 MyClass 的类,这个类下有一个实例 myobject 。当你调用一个这个对象的方法,如 myobject.method(arg1, arg2) 时,Python 将会自动将其转换成 MyClass.method(myobject, arg1, arg2) ——这就是 self 的全部特殊之处所在。这同时意味着,如果你有一个没有参数的方法,你依旧必须拥有一个参数—— self

class Person:
    def say_hi(self):#say_hi 这一方法不需要参数,但是依旧在函数定义中拥有 self 变量
        print("hello")
p = Person()
p.say_hi()
# 前面两行同样可以写作
# Person().say_hi()

输出:

hello

3、__init__ 方法(“__init__”前后分别有两个下划线!!!

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去。__init__ 方法会在类的对象被实例化(Instantiated)时立即运行。这一方法可以对任何你想进行操作的目标对象进行初始化(Initialization)操作。由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name等属性绑上去:

输入:

class Person:
    def __init__(self,name):#下划线要打两次
        self.name=name
    def say_hi(self):
        print('hello',self.name)
p = Person('wqw')
p.say_hi()
#前两句也可以写成p = Person('wqw').say_hi()

输出

hello 李四

self.name 中的点号意味着这个叫作“name”的东西是某个叫作“self”的对象的一部分,而另一个 name 则是一个局部变量。我们不会显式地调用 __init__ 方法。 这正是这个方法的特殊之处所在。

4、类变量与对象变量

类变量(Class Variable)是共享的(Shared)——它们可以被属于该类的所有实例访问。该类变量只拥有一个副本,当任何一个对象对类变量作出改变时,发生的变动将在其它所有实例中都会得到体现。
对象变量(Object variable)由类的每一个独立的对象或实例所拥有。在这种情况下,每个对象都拥有属于它自己的字段的副本,也就是说,它们不会被共享,也不会以任何方式与其它不同实例中的相同名称的字段产生关联。

# -*- coding: utf-8 -*-
class Robot:
    """表示有一个带有名字的机器人。"""
    population = 0
    def __init__(self, name):
        """初始化数据"""
        self.name = name
        print("(Initializing {})".format(self.name))
        # 当有人被创建时,机器人
        # 将会增加人口数量
        Robot.population += 1
        
    def die(self):
        """我挂了。"""
        print("{} is being destroyed!".format(self.name))
        Robot.population -= 1
        if Robot.population == 0:
            print("{} was the last one.".format(self.name)) 
        else:
            print("There are still {:d} robots working.".format(Robot.population))
            
    def say_hi(self):
        """来自机器人的诚挚问候
        没问题,你做得到。"""
        print("Greetings, my masters call me {}.".format(self.name))
        
    @classmethod
    def how_many(cls):
        """打印出当前的人口数量"""
        print("We have {:d} robots.".format(cls.population))
    
droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()

droid2 = Robot("C-3PO")
droid2.say_hi()
Robot.how_many()

print("\nRobots can do some work here.\n")

print("Robots have finished their work. So let's destroy them.")

droid1.die()
droid2.die()
Robot.how_many()

输出:

(Initializing R2-D2)
Greetings, my masters call me R2-D2.
We have 1 robots.
(Initializing C-3PO)
Greetings, my masters call me C-3PO.
We have 2 robots.

Robots can do some work here.

Robots have finished their work. So let's destroy them.
R2-D2 is being destroyed!
There are still 1 robots working.
C-3PO is being destroyed!
C-3PO was the last one.
We have 0 robots.

当一个对象变量与一个类变量名称相同时,类变量将会被隐藏。

除了 Robot.popluation ,我们还可以使用 self.__class__.population ,因为每个对象都通过self.__class__ 属性来引用它的类。

只能使用 self 来引用同一对象的变量与方法。这被称作属性引用(Attribute Reference)。

how_many 实际上是一个属于类而非属于对象的方法。这就意味着我们可以将它定义为一个classmethod(类方法) 或是一个 staticmethod(静态方法) ,这取决于我们是否需要知道这一方法属于哪个类。由于我们已经引用了一个类变量,因此我们使用 classmethod(类方法) 。

5、访问限制

所有的类成员都是公开的。但有一个例外:如果你使用数据成员并在其名字中使用双下划线作为前缀,形成诸如 __privatevar 这样的形式,Python 会使用名称调整(Namemangling)来使其有效地成为一个私有变量。

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。请记得这只是一个约定,Python 并不强制如此(除了双下划线前缀这点)。

定义一个student类如下:

class Student():

    def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s: %s' % (self.name, self.score))
        
bart = Student('Bart Simpson', 59)
bart.print_score()

在student内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。但是,从前面student类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:'

In [23]: bart.score
Out[23]: 59

In [24]: bart.score=99

In [23]:bart.score
Out[25]: 99

实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student():

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))
bart = Student('Bart Simpson', 59)

输出:

In [33]: bart.__name
Traceback (most recent call last):

  File "<ipython-input-33-1e120ece361e>", line 1, in <module>
    bart.__name

AttributeError: 'Student' object has no attribute '__name'

这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法

class Student():

    ...
        
    def get_name(self):
        return self.__name
    
    def get_score(self):
        return self.__score
    

如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:

class Student():

    ...
    
    def set__score(self,score):
        self.__score=score

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

In [37]: bart._Student__name
Out[37]: 'Bart Simpson'

但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

6、继承

面向对象编程的一大优点是对代码的重用(Reuse),重用的一种实现方法就是通过继承(Inheritance)机制。继承最好是想象成在类之间实现类型与子类型(Type and Subtype)关系的工具。
现在假设你希望编写一款程序来追踪一所大学里的老师和学生。有一些特征是他们都具有的,例如姓名、年龄和地址。另外一些特征是他们独有的,一如教师的薪水、课程与假期,学生的成绩和学费。
你可以为每一种类型创建两个独立的类,并对它们进行处理。但增添一条共有特征就意味着将其添加进两个独立的类。这很快就会使程序变得笨重。一个更好的方法是创建一个公共类叫作 SchoolMember ,然后让教师和学生从这个类中继承(Inherit),也就是说他们将成为这一类型(类)的子类型,而我们就可以向这些子类型中添加某些该类独有的特征。

在上文设想的情况中, SchoolMember 类会被称作基类(Base Class) 或是超类(Superclass)。 Teacher 和 Student 类会被称作派生类(Derived Classes) 或是子类(Subclass)。

class SchoolMember:
    def __init__(self,school,name,age):
        self.name=name
        self.age=age
        self.school=school
        print('(Initialized SchoocMember:{})'.format(self.name))
        
    def tell(self):
        print('school:"{}" Name:"{}" Age:"{}"'.format(self.school, self.name, self.age), end=" ")
        
class Teacher(SchoolMember):
    def __init__(self,school,name,age,salary):
        SchoolMember.__init__(self,school,name,age)
        self.salary=salary
        
    def tell(self):
        SchoolMember.tell(self)
        print('salary:"{}"'.format(self.salary))
                    
class Student(SchoolMember):
    def __init__(self,school,name,age,score):
        SchoolMember.__init__(self,school,name,age)
        self.score=score
        
    def tell(self):
        SchoolMember.tell(self)
        print('score:"{}"'.format(self.score))           

t = Teacher('cnu','Mrs. Shrividya', 40, 30000)
s = Student('cnu','Swaroop', 25, 75)               
        
# 打印一行空白行
print()

members = [t, s]
for member in members:
    # 对全体师生工作
    member.tell()

要想使用继承,在定义类时我们需要在类后面跟一个包含基类名称的元组。然后,我们会注意到基类的 __init__ 方法是通过 self 变量被显式调用的,因此我们可以初始化对象的基类部分。

下面这一点很重要,需要牢记——因为我们在 Teacher 和 Student 子类中定义了__init__ 方法,Python 不会自动调用基类 SchoolMember 的构造函数,你必须自己显式地调用它,我们可以通过在方法名前面加上基类名作为前缀,再传入 self 和其余变量,来调用基类的方法。相反,如果我们没有在一个子类中定义一个 __init__ 方法,Python 将会自动调用基类的构造函数。

在这里你需要注意,当我们使用 SchoolMember 类的 tell 方法时,我们可以将 Teacher 或Student 的实例看作 SchoolMember 的实例。
同时,你会发现被调用的是子类型的 tell 方法,而不是 SchoolMember 的 tell 方法。理解这一问题的一种思路是 Python 总会从当前的实际类型中开始寻找方法,在本例中即是如此。如果它找不到对应的方法,它就会在该类所属的基本类中依顺序逐个寻找属于基本类的方法,这个基本类是在定义子类时后跟的元组指定的。

如果继承元组(Inheritance Tuple)中有超过一个类,这种情况就会被称作多重继承(Multiple Inheritance)。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值