美文网首页程序员Python
[译]Python提高:Python类和面向对象编程

[译]Python提高:Python类和面向对象编程

作者: lakerszhy | 来源:发表于2017-09-12 16:59 被阅读760次

    原文作者:Jeff Knupp

    原文链接:这里

    <font color=red>class</font>是Python的基础构建快。它是很多流行的程序和库,以及Python标准库的基础依托。理解类是什么,什么时候使用,以及它们如何有用至关重要,这也是本文的目的。在这个过程中,我们会探讨“面向对象编程”的含义,以及它与Python类之间的联系。

    一切都是对象...

    <font color=red>class</font>关键字究竟是什么?跟它基于函数的<font color=red>def</font>表兄弟类似,它用于定义事物。<font color=red>def</font>用来定义函数,<font color=red>class</font>用来定义。什么是类?就是一个数据和函数(在类中定义时,通常叫做“方法”)的逻辑分组。

    “逻辑分组”是什么意思?一个类可以包含我们希望的任何数据和函数(方法)。我们尝试创建事物之间有逻辑联系的类,而不是把随机的事物放在“类”名下面。很多时候,类都是基于真实世界的物体(比如<font color=red>Customer</font>和<font color=red>Product</font>)。其它时候,类基于系统中的概念,比如<font color=red>HTTPRequest</font>和<font color=red>Owner</font>。

    不管怎么样,类是一种建模技术,一种思考程序的方式。当你用这种方式思考和实现你的系统时,被称为使用面向对象编程。“类”和“对象”经常互换使用,但实际上它们并不相同。理解它们是什么和它们是如何工作的关键是理解它们之间的区别。

    ..所以一切都有一个类?

    类可以看做是创建对象的蓝图。当我使用<font color=red>class</font>关键字定义一个Customer类时,我并没有真正创建一个顾客。相反,我创建的是构建顾客对象的说明手册。让我们看以下示例代码:

    class Customer(object):
        """A customer of ABC Bank with a checking account. Customers have the
        following properties:
    
        Attributes:
            name: A string representing the customer's name.
            balance: A float tracking the current balance of the customer's account.
        """
    
        def __init__(self, name, balance=0.0):
            """Return a Customer object whose name is *name* and starting
            balance is *balance*."""
            self.name = name
            self.balance = balance
    
        def withdraw(self, amount):
            """Return the balance remaining after withdrawing *amount*
            dollars."""
            if amount > self.balance:
                raise RuntimeError('Amount greater than available balance.')
            self.balance -= amount
            return self.balance
    
        def deposit(self, amount):
            """Return the balance remaining after depositing *amount*
            dollars."""
            self.balance += amount
            return self.balance
    

    <font color=red>class Customer(object)</font>并没有创建一个新的顾客。我们只是定义了一个<font color=red>Customer</font>,并不意味着创建了一个顾客;我们仅仅勾勒出创建<font color=red>Customer</font>对象的蓝图。用正确的参数数量(去掉<font color=red>self</font>,我们马上会讨论)调用类的<font color=red>__init__</font>方法可以创建一个顾客。

    因此,要使用通过<font color=red>class Customer</font>(用于创建<font color=red>Customer</font>对象)定义的“蓝图”,可以把类名看做一个函数来调用:<font color=red>jeff = Customer('Jeff Knupp', 1000.0)</font>。这行代码表示:使用<font color=red>Customer</font>蓝图创建一个新对象,并把它指向<font color=red>jeff</font>。

    被称为实例的<font color=red>jeff</font>对象是<font color=red>Customer</font>的实现版本。我们调用<font color=red>Customer()</font>之前,不存在<font color=red>Customer</font>对象。当然,我们可以创建任意多个<font color=red>Customer</font>对象。但是不管我们创建多少<font color=red>Customer</font>实例,仍然只有一个<font color=red>Customer</font>

    <font color=red>self</font>?

    对应所有<font color=red>Customer</font>方法来说,<font color=red>self</font>参数是什么?当然,它是实例。换一种方式,像<font color=red>withdraw</font>这样的方法,定义了从某些抽象顾客的账户中取钱的指令。调用<font color=red>jeff.withdraw(1000.0)</font>把这些指令用在<font color=red>jeff</font>实例上。

    所以,当我们说:<font color=red>def withdraw(self, amount):</font>,我们的意思是:这是你如何从一个顾客对象(我们称为<font color=red>self</font>)和一个美元数字(我们称为<font color=red>amount</font>)取钱。<font color=red>self</font>是<font color=red>Customer</font>的实例,在它上面调用<font color=red>withdraw</font>。这也不是我做类比。<font color=red>jeff.withdraw(1000.0)</font>只是<font color=red>Customer.withdraw(jeff, 1000.0)</font>的简写,也是完全有限的代码。

    <font color=red>__init__</font>

    <font color=red>self</font>可能对其它方法也有意义,但是<font color=red>__init__</font>呢?当我们调用<font color=red>__init__</font>时,我们在创建一个对象的过程中,为什么已经有了<font color=red>self</font>?尽管不完全适合,Python还是允许我们扩展<font color=red>self</font>模式到对象的构造。想象<font color=red>jeff = Customer('Jeff Knupp', 1000.0)</font>等价于<font color=red>jeff = Customer(jeff, 'Jeff Knupp', 1000.0)</font>;传入的<font color=red>jeff</font>也是同样的结果。

    这就是为什么调用<font color=red>__init__</font>时,我们通过<font color=red>self.name = name</font>来初始化对象。记住,因为<font color=red>self</font>是实例,所以它等价于<font color=red>jeff.name = name</font>,它等价于<font color=red>jeff.name = 'Jeff Knupp'</font>。同样的,<font color=red>self.balance = balance</font>等价于<font color=red>jeff.balance = 1000.0</font>。这两行代码之后,我们认为<font color=red>Customer</font>对象已经“初始化”,可以被使用。

    完成<font color=red>__init__</font>后,调用者可以假设对象已经可以使用。也就是,调用<font color=red>jeff = Customer('Jeff Knupp', 1000.0)</font>后,我们可以在<font color=red>jeff</font>上调用<font color=red>deposit</font>和<font color=red>withdraw</font>;<font color=red>jeff</font>是一个完全初始化的对象。

    我们定义了另外一个稍微不同的<font color=red>Customer</font>类:

    class Customer(object):
        """A customer of ABC Bank with a checking account. Customers have the
        following properties:
    
        Attributes:
            name: A string representing the customer's name.
            balance: A float tracking the current balance of the customer's account.
        """
    
        def __init__(self, name):
            """Return a Customer object whose name is *name*.""" 
            self.name = name
    
        def set_balance(self, balance=0.0):
            """Set the customer's starting balance."""
            self.balance = balance
    
        def withdraw(self, amount):
            """Return the balance remaining after withdrawing *amount*
            dollars."""
            if amount > self.balance:
                raise RuntimeError('Amount greater than available balance.')
            self.balance -= amount
            return self.balance
    
        def deposit(self, amount):
            """Return the balance remaining after depositing *amount*
            dollars."""
            self.balance += amount
            return self.balance
    

    它看起来是一个合理的替代者;在使用实例之前,只需要简单的调用<font color=red>set_balance</font>。但是,没有一种方式可以告诉调用者这么做。即使我们在文档中说明了,也不能强制调用者在调用<font color=red>jeff.withdraw(100.0)</font>之前调用<font color=red>jeff.set_balance(1000.0)</font>。<font color=red>jeff</font>实例在调用<font color=red>jeff.set_balance</font>之前没有<font color=red>balance</font>属性,这意味着对象没有“完全”初始化。

    简单来说,不要在<font color=red>__init__</font>方法之外引入新的属性,否则你会给调用一个没有完全初始化的对象。当然也有例外,但这是一个需要记住的原则。这是对象一致性这个大概念的一部分:不应该有任何一系列的方法调用可能导致对象进入没有意义的状态。

    不变性(比如“账户余额总是非负数”)应该在方法进入和退出时都保留。对象不可能通过调用它的方法进入无效状态。不用说,一个对象也应该从一个有效的状态开始,这就是为什么在<font color=red>__init__</font>方法中初始化所有内容是很重要的。

    实例属性和方法

    定义在类中的函数称为“方法”。方法可以访问包含在对象实例中的所有数据;它们可以访问和修改之前在<font color=red>self</font>上设置的任何内容。因为它们使用<font color=red>self</font>,所以需要使用类的一个实例。基于这个原因,它们通常被称为“实例方法”。

    如果有“实例方法”,一定也会有其它类型的方法,对吧?是的,确实有,但这些方法有些深奥。我们会在这里简略的介绍一下,但是可以更深入的研究这些主题。

    静态方法

    类属性是在类级别上设置的属性,相对的是实例级别。普通属性在<font color=red>__init__</font>方法中引入,但有些属性适用于所有实例。例如,思考下面<font color=red>Car</font>对象的定义:

    class Car(object):
    
        wheels = 4
    
        def __init__(self, make, model):
            self.make = make
            self.model = model
    
    mustang = Car('Ford', 'Mustang')
    print mustang.wheels
    # 4
    print Car.wheels
    # 4
    

    不管<font color=red>make</font>和<font color=red>model</font>是什么,一辆<font color=red>Car</font>总是有四个<font color=red>Wheels</font>。实例方法可以通过跟访问普通属性一样访问这些属性:通过<font color=red>self</font>(比如,<font color=red>self.wheels</font>)。

    有一种称为静态方法的方法,它们不能访问<font color=red>self</font>。跟类属性类似,它们不需要实例就能工作。因为实例总是通过<font color=red>self</font>引用,所以静态方法没有<font color=red>self</font>参数。

    下面是<font color=red>Car</font>类的一个有效的静态方法:

    class Car(object):
        ...
        def make_car_sound():
            print 'VRooooommmm!'
    

    不管我们拥有什么类型的汽车,它总是发出相同的声音。为了说明这个方法不应该接收实例作为第一个参数(比如“普通”方法的<font color=red>self</font>),可以使用<font color=red>@staticmethod</font>装饰器,把我们的定义变成:

    class Car(object):
        ...
        @staticmethod
        def make_car_sound():
            print 'VRooooommmm!'
    

    类方法

    静态方法的一个变种是类方法。它传递,而不是实例作为第一个参数。它也使用装饰器定义:

    class Vehicle(object):
        ...
        @classmethod
        def is_motorcycle(cls):
            return cls.wheels == 2
    

    现在类方法可能没有太大的意义,但它通常与下一个主题联系在一起:继承

    继承

    面向对象编程作为建模工具非常有用,引入继承的概念后,它真正变强大了。

    继承是“子”类衍生“父”类的数据和行为的过程。有一个实例可以明确的帮助我们理解。

    想象我们经营了一家汽车销售店。我们销售所有类型的车辆,从摩托车到卡车。我们通过价格与竞争对手区分开来。特别是我们如何确定车辆的价格:$5000 * 一台车辆拥有的车轮数。我们也喜欢回购车辆。我们提供统一的价格 - 车辆行驶里程的10%。卡车的价格是$10,000,汽车是$8,000,摩托车是$4,000。

    如果我们想用面对对象技术为汽车销售店创建一个销售系统,应该怎么做?对象是什么?我们可能有一个<font color=red>Sale</font>类,一个<font color=red>Customer</font>类,一个<font color=red>Inventor</font>类等等,但我们肯定有一个<font color=red>Car</font>,<font color=red>Truck</font>和<font color=red>Motorcycle</font>类。

    这些类应该是什么样的?用我们已经学会的知识,以下是<font color=red>Car</font>类的一种实现:

    class Car(object):
        """A car for sale by Jeffco Car Dealership.
    
        Attributes:
            wheels: An integer representing the number of wheels the car has.
            miles: The integral number of miles driven on the car.
            make: The make of the car as a string.
            model: The model of the car as a string.
            year: The integral year the car was built.
            sold_on: The date the vehicle was sold.
        """
    
        def __init__(self, wheels, miles, make, model, year, sold_on):
            """Return a new Car object."""
            self.wheels = wheels
            self.miles = miles
            self.make = make
            self.model = model
            self.year = year
            self.sold_on = sold_on
    
        def sale_price(self):
            """Return the sale price for this car as a float amount."""
            if self.sold_on is not None:
                return 0.0  # Already sold
            return 5000.0 * self.wheels
    
        def purchase_price(self):
            """Return the price for which we would pay to purchase the car."""
            if self.sold_on is None:
                return 0.0  # Not yet sold
            return 8000 - (.10 * self.miles)
    
        ...
    

    看起来非常合理。当然,类中可能还有其它方法,但我已经展示了两个我们感兴趣的方法:<font color=red>sale_price</font>和<font color=red>purchase_price</font>。我们之后会看到为什么这些很重要。

    我们已经有了<font color=red>Car</font>类,也许我们应该创建<font color=red>Truck</font>类。我们按同样的方式创建:

    class Truck(object):
        """A truck for sale by Jeffco Car Dealership.
    
        Attributes:
            wheels: An integer representing the number of wheels the truck has.
            miles: The integral number of miles driven on the truck.
            make: The make of the truck as a string.
            model: The model of the truck as a string.
            year: The integral year the truck was built.
            sold_on: The date the vehicle was sold.
        """
    
        def __init__(self, wheels, miles, make, model, year, sold_on):
            """Return a new Truck object."""
            self.wheels = wheels
            self.miles = miles
            self.make = make
            self.model = model
            self.year = year
            self.sold_on = sold_on
    
        def sale_price(self):
            """Return the sale price for this truck as a float amount."""
            if self.sold_on is not None:
                return 0.0  # Already sold
            return 5000.0 * self.wheels
    
        def purchase_price(self):
            """Return the price for which we would pay to purchase the truck."""
            if self.sold_on is None:
                return 0.0  # Not yet sold
            return 10000 - (.10 * self.miles)
    
        ...
    

    几乎跟<font color=red>Car</font>类一模一样。编程中最重要的原则之一(通常不只是处理对象时)是“DRY”或者“Don't Repeat Yourself”。确定无疑,我们在这里重复了。实际上,<font color=red>Car</font>类和<font color=red>Truck</font>类只有一个字符不同(除了注释)。

    出什么事了?我们哪里做错了?我们的主要问题是我们直奔概念:<font color=red>Car</font>和<font color=red>Truck</font>是真实的事物,直觉让有形的对象成为类。但是它们共享这么多数据和功能,似乎我们可以在这里引入一个抽象。没错,它就是<font color=red>Vehicle</font>。

    抽象类

    <font color=red>Vehicle</font>不是真实世界的对象。而是一个概念,它包含某些真实世界中的对象(比如汽车,卡车和摩托车)。我们可以用这个事实来移除重复代码,即每个对象都被看做是一台车辆。通过定义<font color=red>Vehicle</font>类达到目的:

    class Vehicle(object):
        """A vehicle for sale by Jeffco Car Dealership.
    
        Attributes:
            wheels: An integer representing the number of wheels the vehicle has.
            miles: The integral number of miles driven on the vehicle.
            make: The make of the vehicle as a string.
            model: The model of the vehicle as a string.
            year: The integral year the vehicle was built.
            sold_on: The date the vehicle was sold.
        """
    
        base_sale_price = 0
    
        def __init__(self, wheels, miles, make, model, year, sold_on):
            """Return a new Vehicle object."""
            self.wheels = wheels
            self.miles = miles
            self.make = make
            self.model = model
            self.year = year
            self.sold_on = sold_on
    
    
        def sale_price(self):
            """Return the sale price for this vehicle as a float amount."""
            if self.sold_on is not None:
                return 0.0  # Already sold
            return 5000.0 * self.wheels
    
        def purchase_price(self):
            """Return the price for which we would pay to purchase the vehicle."""
            if self.sold_on is None:
                return 0.0  # Not yet sold
            return self.base_sale_price - (.10 * self.miles)
    

    通过替换<font color=red>class Car(object)</font>中的<font color=red>object</font>,我们可以让<font color=red>Car</font>和<font color=red>Truck</font>类继承<font color=red>Vehicle</font>类。括号中的类表示从哪个类继承(<font color=red>object</font>实际上是“没有继承”。我们一会儿讨论为什么这么写)。

    现在我们可以直截了当的定义<font color=red>Car</font>和<font color=red>Truck</font>:

    class Car(Vehicle):
    
        def __init__(self, wheels, miles, make, model, year, sold_on):
            """Return a new Car object."""
            self.wheels = wheels
            self.miles = miles
            self.make = make
            self.model = model
            self.year = year
            self.sold_on = sold_on
            self.base_sale_price = 8000
    
    
    class Truck(Vehicle):
    
        def __init__(self, wheels, miles, make, model, year, sold_on):
            """Return a new Truck object."""
            self.wheels = wheels
            self.miles = miles
            self.make = make
            self.model = model
            self.year = year
            self.sold_on = sold_on
            self.base_sale_price = 10000
    

    这样可以工作了,但还有一些问题。首先我们仍然有很多重复的代码。最终我们会处理完所有重复的代码。其次,更大的问题是,我们引入了<font color=red>Vehicle</font>类,但我们真的允许调用者创建<font color=red>Vehicle</font>对象(而不是<font color=red>Car</font>和<font color=red>Truck</font>)?<font color=red>Vehicle</font>仅仅是一个概念,不是真实的事物,所以下面代码的意义是:

    v = Vehicle(4, 0, 'Honda', 'Accord', 2014, None)
    print v.purchase_price()
    

    <font color=red>Vehicle</font>没有<font color=red>base_sale_price</font>,只有各个子类(比如<font color=red>Car</font>和<font color=red>Truck</font>)有。问题在于<font color=red>Vehicle</font>应该是一个Abstract Base ClassAbstract Base Class是只可以被继承的类;不能创建ABC的实例。这意味着如果<font color=red>Vehicle</font>是一个ABC,那么下面的代码就是非法的:

    v = Vehicle(4, 0, 'Honda', 'Accord', 2014, None)
    

    禁止这一点是有意义的,因为我们从来不会直接使用<font color=red>Vehicle</font>。我们只想用它抽取一些通用的数据和行为。我们如何让一个类成为ABC?很简单!<font color=red>abc</font>模块包括一个称为<font color=red>ABCMeta</font>的元类。设置一个类的元类为<font color=red>ABCMeta</font>,并让其中一个方法为虚拟的,就能让类成为一个ABCABC规定,虚拟方法必须在子类中存在,但不是必须要实现。例如,<font color=red>Vehicle</font>类可以如下定义:

    from abc import ABCMeta, abstractmethod
    
    class Vehicle(object):
        """A vehicle for sale by Jeffco Car Dealership.
    
    
        Attributes:
            wheels: An integer representing the number of wheels the vehicle has.
            miles: The integral number of miles driven on the vehicle.
            make: The make of the vehicle as a string.
            model: The model of the vehicle as a string.
            year: The integral year the vehicle was built.
            sold_on: The date the vehicle was sold.
        """
    
        __metaclass__ = ABCMeta
    
        base_sale_price = 0
    
        def sale_price(self):
            """Return the sale price for this vehicle as a float amount."""
            if self.sold_on is not None:
                return 0.0  # Already sold
            return 5000.0 * self.wheels
    
        def purchase_price(self):
            """Return the price for which we would pay to purchase the vehicle."""
            if self.sold_on is None:
                return 0.0  # Not yet sold
            return self.base_sale_price - (.10 * self.miles)
    
        @abstractmethod
        def vehicle_type():
            """"Return a string representing the type of vehicle this is."""
            pass
    

    因为<font color=red>vehicle_type</font>是一个<font color=red>abstractmethod</font>,所以我们不能直接创建<font color=red>Vehicle</font>实例。只要<font color=red>Car</font>和<font color=red>Truck</font>从<font color=red>Vehicle</font>继承,定义了<font color=red>vehicle_type</font>,我们就能实例化这些类。

    返回<font color=red>Car</font>类和<font color=red>Truck</font>类中的重复代码,看看我们是否可以把通用的功能提升到基类<font color=red>Vehicle</font>中:

    from abc import ABCMeta, abstractmethod
    class Vehicle(object):
        """A vehicle for sale by Jeffco Car Dealership.
    
    
        Attributes:
            wheels: An integer representing the number of wheels the vehicle has.
            miles: The integral number of miles driven on the vehicle.
            make: The make of the vehicle as a string.
            model: The model of the vehicle as a string.
            year: The integral year the vehicle was built.
            sold_on: The date the vehicle was sold.
        """
    
        __metaclass__ = ABCMeta
    
        base_sale_price = 0
        wheels = 0
    
        def __init__(self, miles, make, model, year, sold_on):
            self.miles = miles
            self.make = make
            self.model = model
            self.year = year
            self.sold_on = sold_on
    
        def sale_price(self):
            """Return the sale price for this vehicle as a float amount."""
            if self.sold_on is not None:
                return 0.0  # Already sold
            return 5000.0 * self.wheels
    
        def purchase_price(self):
            """Return the price for which we would pay to purchase the vehicle."""
            if self.sold_on is None:
                return 0.0  # Not yet sold
            return self.base_sale_price - (.10 * self.miles)
    
        @abstractmethod
        def vehicle_type(self):
            """"Return a string representing the type of vehicle this is."""
            pass
    

    现在<font color=red>Car</font>和<font color=red>Truck</font>类变成:

    class Car(Vehicle):
        """A car for sale by Jeffco Car Dealership."""
    
        base_sale_price = 8000
        wheels = 4
    
        def vehicle_type(self):
            """"Return a string representing the type of vehicle this is."""
            return 'car'
    
    class Truck(Vehicle):
        """A truck for sale by Jeffco Car Dealership."""
    
        base_sale_price = 10000
        wheels = 4
    
        def vehicle_type(self):
            """"Return a string representing the type of vehicle this is."""
            return 'truck'
    

    这完全符合我们的直觉:就我们的系统而言,汽车和卡车之间的唯一区别是基础售价。

    定义一个<font color=red>Motocycle</font>类非常简单:

    class Motorcycle(Vehicle):
        """A motorcycle for sale by Jeffco Car Dealership."""
    
        base_sale_price = 4000
        wheels = 2
    
        def vehicle_type(self):
            """"Return a string representing the type of vehicle this is."""
            return 'motorcycle'
    

    继承和LSP

    尽管看起来我们用继承处理了重复,但我们真正做的是简单的提供适当级别的抽象。抽象是理解继承的关键。我们已经看到使用继承的一个附带作用是减少重复的代码,但从调用者的角度来看呢?使用继承如何改变代码?

    事实证明有一点。想象我们有两个类:<font color=red>Dog</font>和<font color=red>Person</font>,我们想写一个函数,它接收任何两种对象类型,并打印该实例是否可以说话(狗不能,人可以)。我们可能这么编写代码:

    def can_speak(animal):
        if isinstance(animal, Person):
            return True
        elif isinstance(animal, Dog):
            return False
        else:
            raise RuntimeError('Unknown animal!')
    

    只有两种类型的动物时没问题,但是如何有20种呢,或者200种?那么<font color=red>if...elif</font>会相当长。

    这里关键是<font color=red>can_speak</font>不应该关心处理的动物类型,动物类本身应该告诉我们它能否说话。通过引入基类<font color=red>Animal</font>,其中定义<font color=red>can_speak</font>,可以避免函数的类型检查。只要知道是传进来的是<font color=red>Animal</font>,确定能否说话很简单:

    def can_speak(animal):
        return animal.can_speak()
    

    这是因为<font color=red>Person</font>和<font color=red>Dog</font>(或者其它任何从<font color=red>Animal</font>继承的类)遵循Liskov Substitution Principle。这表示我们可以在希望父类(<font color=red>Animal</font>)的地方,使用子类(比如<font color=red>Person</font>或<font color=red>Dog</font>)替换。这听起来很简单,但它是interface的基础。

    总结

    希望你们学会了什么是Python类,为什么它们很有用,以及如何使用。类和面向对象编程很深奥。确实,它涉及计算机科学的核心。本文不是对类的详细研究,也不应该是你的唯一参考。网络上有数以千计的OOP和类的解释,如果本文对你不合适,搜索会让你找到更适合你的。

    一如既往,欢迎在评论中更正和讨论。只要保持礼貌就行。

    相关文章

      网友评论

        本文标题:[译]Python提高:Python类和面向对象编程

        本文链接:https://www.haomeiwen.com/subject/isocsxtx.html