(七)《A Byte of Python》 ——面向对象编程

       在至今我们编写的所有程序中,我们曾围绕函数设计我们的程序,也就是那些能够处理数据的代码块。这被称作面向过程(Procedure-oriented) 的编程方式。还有另外一种组织起你的程序的方式,它将数据与功能进行组合,并将其包装在被称作对象的东西内。在大多数情况下,你可以使用过程式编程,但是当你需要编写一个大型程序或面对某一更适合此方法的问题时,你可以考虑使用面向对象式的编程技术。类与对象是面向对象编程的两个主要方面。一个类(Class) 能够创建一种新的类型(Type),其中对象(Object)就是类的实例(Instance) 。可以这样来类比:你可以拥有类型 int的变量,也就是说存储整数的变量是int类的实例(对象)调用对象对应的关联函数即对象的方法

        对象可以使用属于对象的原始变量来存储数据。属于某个对象或类的变量被称作字段Field) 。对象还可以通过使用属于类的函数来拥有某些功能。这些函数被称作类的方法Method) 。这一术语特别重要,因为它有助于我们区分函数与变量,两者皆为独立且它们皆从属于某个类或对象。总的来说,字段与方法都可以看作其所属的类的属性Attribute) 。字段有两种类型——它们属于某一类的各个实例或对象,或是从属于某一类本身。它们被分别称作实例变量Instance Variables) 与类变量Class Variables) 。通过 class关键字可以创建一个类。这个类的字段与方法可以在缩进代码块中予以列出。

1. self

       类方法与普通函数只有一种特定的区别——前者必须有一个额外的名字,这个名字必须添加到参数列表的开头,但是你不用在你调用这个功能时为这个参数赋值,Python会为它赋值。这种特定的变量引用的是对象本身,按照惯例,它被赋予self这一名称。假设你有一个MyClass的类,这个类下有一个实例myobject。当你调用一个这个对象的方法,如myobject.method(arg1, arg2) 时,Python将会自动将其转换成MyClass.method(myobject, arg1, arg2)——这就是self的全部特殊之处所在。这同时意味着,如果你有一个没有参数的功能,你依旧必须拥有一个参数——self 

2. 类

class Person:
    pass # 一个空的代码块
p = Person()
print(p)
$ python oop_simplestclass.py
<__main__.Person instance at 0x10171f518>

       通过使用class语句与这个类的名称来创建一个新类。在它之后是一个缩进的语句块,代表这个类的主体。在本案例中,我们创建的是一个空代码块,使用pass语句予以标明。然后,我们通过采用类的名称后跟一对括号的方法,给这个类创建一个对象。为了验证我们的操作是否成功,我们通过直接将它们打印出来来确认变量的类型。结果告诉我们我们在Person类的__main__模块中拥有了一个实例。要注意到在本例中还会打印出计算机内存中存储你的对象的地址。

3. 方法

class Person:
    def say_hi(self):
        print('Hello, how are you?')
p = Person()
p.say_hi()
# 同样可以写作Person().say_hi()
$ python oop_method.py
Hello, how are you?

4. __init__方法 

        类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。__init__方法会在类的对象被实例化(Instantiated) 时立即运行。这一方法可以对任何你想进行操作的目标对象进行初始化(Initialization) 操作。这里你要注意在 init 的前后加上的双下划线。 __init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

class Person:
    def __init__(self, name):
        self.name = name
    def say_hi(self):
        print('Hello, my name is', self.name)
p = Person('Swaroop')
p.say_hi()
# 同时也能写作Person('Swaroop').say_hi()
$ python oop_init.py
Hello, my name is Swaroop

        在本例中,我们定义 __init__ 方法用以接受 name参数(与更普遍的 self 一道) 。在这里,我们创建了一个字段,同样称为 name。要注意到尽管它们的名字都是“name”,但这是两个不相同的变量。虽说如此,但这并不会造成任何问题,因为点号self.name 意味着这个叫作“name”的东西是某个叫作“self”的对象的一部分,而另一个name 则是一个局部变量。当我们在Person 类下创建新的实例 p 时,我们采用的方法是先写下类的名称,后跟括在括号中的参数,形如:p = Person('Swaroop') 。我们不会显示地调用__init__ 方法。 这正是这个方法的特殊之处所在。 

5. 类变量与对象变量

        数据部分——也就是字段——只不过是绑定(Bound) 到类与对象的命名空间(Namespace)的普通变量。这就代表着这些名称仅在这些类与对象所存在的上下文中有效。这就是它们被称作的普通变量。这就代表着这些名称仅在这些类与对象所存在的上下文中有效。这就是它们被称作命名空间的原因。字段(Filed) 有两种类型——类变量与对象变量,它们根据究竟是类还是对象拥有这些变量来进行分类。类变量(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()
$ python oop_objvar.py
(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.

        在本例中, population属于Robot类,因此它是一个类变量。name 变量属于一个对象(通过使用self 分配) ,因此它是一个对象变量。因此,我们通过Robot.population而非 self.population引用 population 类变量。我们对name 对象变量采用 self.name 标记法加以称呼,这是这个对象中所具有的方法。要记住这个类变量与对象变量之间的简单区别。注意当一个对象变量与一个类变量名称相同时,类变量将会被隐藏。

       除了Robot.popluation,我们还可以使用self.__class__.population,因为每个对象都通过self.__class__属性来引用它的类。how_many实际上是一个属于类而非属于对象的方法。这就意味着我们可以将它定义为一个classmethod(类方法)或是一个 staticmethod(静态方法),这取决于我们是否知道我们需不需要知道我们属于哪个类。由于我们已经引用了一个类变量,因此我们使用classmethod(类方法)我们使用装饰器Decoratorhow_many 方法标记为类方法。你可以将装饰器想象为调用一个包装器(Wrapper) 函数的快捷方式,因此启用@classmethod装饰器等价于调用:

how_many = classmethod(how_many)

        你会观察到 __init__方法会用以初始化 Robot这一带有名字的实例。在这一方法中,我们将 population 1 往上增长,因为我们多增加了一台机器人。self.name 的值是指定给每个对象的。只能使用self 来引用同一对象的变量与方法。这被称作属性引用(Attribute Reference) 。在本程序中,我们还会看见针对类和方法的文档字符串(DocStrings) 的使用方式。我们可以在运行时通过Robot.__doc__ 访问类的 文档字符串,对于方法的文档字符串,则可以使用Robot.say_hi.__doc__。在die 方法中,我们简单地将Robot.population 的计数按1 向下减少。所有的类成员都是公开的。但有一个例外:如果你使用数据成员并在其名字中使用双下划线作为前缀,形成诸如__privatervar 这样的形式,Python会使用名称调整(Namemangling) 来使其有效地成为一个私有变量。因此,你需要遵循这样的约定:任何在类或对象之中使用的对象其命名应以下划线开头,其它所有非此格式的名称都将是公开的,并可以为其它任何类或对象所使用。

6. 继承

        面向对象编程的一大优点是代码重用Reuse) ,重用的一种实现方法就是通过继承Inheritance) 机制。继承最好是想象成在类之间实现类型与子类型Type and Subtype关系的工具。新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

# coding=UTF-8
class SchoolMember:
    '''代表任何学校里的成员。'''
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print('(Initialized SchoolMember: {})'.format(self.name))
    def tell(self):
        '''告诉我有关我的细节。'''
        print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")
class Teacher(SchoolMember):
    '''代表一位老师。'''
    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age)
        self.salary = salary
        print('(Initialized Teacher: {})'.format(self.name))
    def tell(self):
        SchoolMember.tell(self)
        print('Salary: "{:d}"'.format(self.salary))
class Student(SchoolMember):
    '''代表一位学生。'''
    def __init__(self, name, age, marks):
        SchoolMember.__init__(self, name, age)
        self.marks = marks
        print('(Initialized Student: {})'.format(self.name))
    def tell(self):
        SchoolMember.tell(self)
        print('Marks: "{:d}"'.format(self.marks))
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)
# 打印一行空白行
print()
members = [t, s]
for member in members:
    # 对全体师生工作
    member.tell()
$ python oop_subclass.py
(Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)
Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
Name:"Swaroop" Age:"25" Marks: "75"

        要想使用继承,在定义类 时我们需要在类后面跟一个包含基类名称的元组。然后,我们会注意到基类的__init__ 方法是通过 self 变量被显示调用的,因此我们可以初始化对象的基类部分。下面这一点很重要,需要牢记——因为我们在Teacher Student 子类中定义了__init__方法,Python 不会自动调用基类 SchoolMember 的构造函数,你必须自己显式地调用它。相反,如果我们没有在一个子类中定义一个 __init__方法,Python 将会自动调用基类的构造函数。我们会观察到,我们可以通过在类名前面添加前缀连接类与方法,然后通过 self变量调用并传递任何参数,来调用基类的方法。在这里你需要注意,当我们使用 SchoolMember类的 tell 方法时,我们可以将Teacher Studtne的实例看作 SchoolMember的实例。同时,你会发现被调用的是子类型的 tell方法,而不是 SchoolMembertell 方法。理解这一问题的一种思路是Python 总会从当前的实际类型中开始寻找方法,在本例中即是如此。如果它找不到对应的方法,它就会在该类所属的基本类中依顺序逐个寻找属于基本类的方法,这个基本类是在定义子类时后跟的元组指定的。这里有一条有关术语的注释——如果继承元组(Inheritance Tuple) 中有超过一个类,这种情况就会被称作多重继承Multiple Inheritance) 。end参数用在超类的 tell() 方法的 print 函数中,目的是打印一行并允许下一次打印在同一行继续。














猜你喜欢

转载自blog.csdn.net/muumian123/article/details/79087175