python编程
首发于python编程
面向对象(二)|python类的详解

面向对象(二)|python类的详解

本文通过创建几个类来覆盖python中类的基础知识,主要有如下几个类

  • Animal :各种属性、方法以及属性的修改
  • Dog :将方法转化为属性并操作的方法
  • Cat :私人属性讲解,方法的继承与覆盖
  • Tiger :子类定义时调用父类方法(super的使用)

Animal

python中的一个类中有属性和方法,而二者都分为很多种类型,如下所示

  • 属性分为普通属性和类属性
  • 方法分为普通方法、类方法、静态方法。
  • 具体定义方法和使用见下面的代码和注释,各个属性和方法的使用习惯等见最后的 print_animal 函数打印出的结果

下面是类的定义

class Animal:
    
    # 这里是属性定义
    actually = "animal" # 类属性
    
    def __init__(self, name, age): # 定义实例时,放在括号里的才要指定
        self.name = name # 普通属性,要在__init__方法中定义
        self.age = age
    
    # 下面是方法的定义
    def sleep(self): # 普通方法
        print(self.name, "is sleeping")
        
    def eat(self, food): # 普通方法,另带参数
        print(self.name, "is eating", food)
    
    @classmethod
    def sentence(cls, adv): # 类方法,使用装饰器变成类方法
        print("I am", adv, "an", cls.actually)
    
    @staticmethod
    def other(person, do): # 静态方法
        print(person, "is", do+"ing")
    
    @staticmethod
    def print_animal():
        print("这是之后定义子类的父类,主要讲解最基本的属性、方法以及属性的修改")
        print("类属性actually:属于整个类,每个实例都有的属性,内容相同,创建实例时不需要指定,类和实例都可以调用")
        print("普通属性name age:属于各个实例,用于存储实例数据")
        
        print("普通方法sleep eat:由对象调用,至少一个参数self")
        print("类方法sentence:由类、实例调用,至少一个参数cls,可以引用类属性")
        print("静态方法other:类中的普通函数,可由类、实例调用")
        
        print("修改类属性:用类调用修改,所有实例都更改;用实例调用修改不影响类和其他实例")
        print("修改普通属性:直接赋值即可")

创建实例

# 创建实例调用Animal类
adams = Animal(name="Adams",age=2) # 创建实例
adams.actually # 调用类属性
# 'animal'
Animal.actually # 类调用类属性
# 'animal'
adams.name # 调用普通属性
# 'Adams'

adams.sleep() # 调用普通方法
# Adams is sleeping
adams.eat("meat") # 有参数的普通方法
# Adams is eating meat

adams.sentence("really") # 实例调用类方法
# I am really an animal
Animal.sentence("actually") # 类调用类方法
# I am actually an animal

adams.other("Tim", "play") # 实例调用静态方法
# Tim is playing
Animal.other("Mary", "watch") # 类调用静态方法
# Mary is watching

Animal.actually = "Animal" # 修改类属性
adams.actually
# 'Animal'
adams.actually = "animal"
Animal.actually
# 'Animal'
adams.age = 3 # 普通属性这样就改过来了

Animal.print_animal()

Dog

Dog类是Animal的一个子类,主要讲解三个装饰器进行方法向属性的转换。

转换有两个主要的好处

  • 一是调用没有参数的方法时不再需要加括号,这样的方法就可以当做属性来看
  • 二是这样定义的属性赋值时可以进行判断,防止无效属性的产生

这样的转换有两种方式

  • 一种是通过@property装饰器,这个装饰器系列一共三个,如果只是想调用这个方法可以只使用@property这个装饰器
  • 一种是通过property函数

下面是一个例子,一些说明可以在最后面定义的print_dog方法中查看

class Dog(Animal): # 类的继承
    
    # 只使用@property装饰器与普通函数做对比
    def eating(self):
        print("I am eating")
    
    @property # 用这个装饰器后这个方法调用就可以不加括号,即将其转化为属性
    def running(self): 
        if self.age >= 3 and self.age < 130:
            print("I am running")
        elif self.age > 0 and self.age <3:
            print("I can't run")
        else:
            print("please input true age")
            
    # 三种装饰器,可以获取、设置、删除这样定义的属性
    @property
    def country(self):
        return self._country # 注意这个属性之前从来没有定义过,是在下面的setter中定义的
    
    @country.setter # 用 函数名.setter 的装饰器
    def country(self, value): # 设置这个属性的值
        self._country = value
        
    @country.deleter
    def country(self):
        print("The attr country is deleted")
        
        
    # 用property函数实现和装饰器相同的功能
    def get_city(self):
        return self._city
    
    def set_city(self, value):
        self._city = value
        
    def del_city(self, value):
        del self._city
        
    city = property(get_city, set_city, del_city, "where it is in")
        
    @staticmethod
    def print_dog():
        print("这是Animal的一个子类,主要讲解三个装饰器进行方法向属性的转换")
        print("类继承,创建实例时仍要指定父类的普通属性")
        print("@property装饰器将方法转化为属性方式调用,此时的方法必须只有一个self参数")
        print("使用@property后可以看做一个属性(country),用property函数可以达到相同的效果(city)")
        print("注:city中property第四个参数只是一个说明,用Dog.city.__doc__来调用,即返回 where it is in")

创建实例

david = Dog("David", 2) # 创建实例

# 只用@property的情形
david.eating() # 调用普通方法
# I am eating
david.running # 用过@property装饰器后不需要加括号
# I can't run

dean = Dog("Dean", 4)
dean.running # 在@property的属性中进行判断
# I am running


# @property等三个装饰器
david.country = "America"
print(david.country)
del david.country # 如果这里的不出现_country则这样就可以删除,但是用self.country则真的变成了属性,所以为了区别多定义了一个_country
del david._country # 如今需要再把这个中间变量删除掉才可以
# 无法再调用 david.country

# 不用装饰器,用函数的形式
david.city = "Beijing"
print(david.city) # Beijing

Cat

这里定义了Cat类和它的子类Blackcat,主要说明两个主题:私人属性、方法的继承与覆盖

  • 私人属性的使用是受到一定限制的,这里主要说明在哪些地方可以用,哪些地方不能调用
  • 因为子类继承父类时,会将父类的所有方法都继承过来,但是如果在子类中定义了一个和父类同名的方法,则会将父类的方法覆盖下去,__init__也是如此

定义类

class Cat(Animal):
    
    def __init__(self, weight): # 覆盖了父类的__init__,所以定义实例时不需要指定name和age
        self.__weight = weight
        self._weight = weight + 1
        self.weight = self._weight + 1
    
    def get_myweight(self):
        print("My weight is", self._weight, "kg")
    
    def get_trueweight(self):
        print("Actually the cat's weight is",self.__weight)
    
    @staticmethod
    def print_cat():
        print("这个类是Animal类的子类,也是Blackcat类的父类")
        print("Cat类和Blackcat类结合,主要用于讲解私人属性、方法的继承与覆盖")
        
        print("weight是普通属性,可以在外部访问,即用类调用属性获得,可以被子类内部调用")
        print("_weight这样前面加一个下划线表示希望你不要在外部访问它,但是还是可以访问的,可以被子类内部调用")
        print("__weight这样加两个下划线的是不允许外部访问的,只可以在类中被调用,不可以被子类内部调用")
        
        print("__init__在子类中定义则覆盖了父类中的__init__,不需要指定父类中的属性,也无法调用父类的属性")
        print("在子类中实现和父类同名的方法,也会把父类的方法覆盖掉")
        
class Blackcat(Cat):
    
    def get_weight(self):
        print("My weight is", self.weight)
    
    def get_weight_again(self): # 可以
        print("Actuall my weight is", self._weight)
        
    def get_true_weight(self): # 这个方法因为无法调用self.__weight所以这个方法无法使用
        print("My weight is exactly", self.__weight)
        
    def get_trueweight(self): # 覆盖了Cat父类中定义的方法
        print("My weight is exactly", self._weight+1)

创建实例

# 测试私人变量的外部访问
cole = Cat(5)
cole.weight
# 7
cole._weight
# 6
cole.get_trueweight() # 在类中引用__weight,用这个方法返回
# Actually the cat's weight is 5
cole._Cat__weight # 非要访问也可以,其实是python解释器把__weight改成了_Cat__weight
# 5

# 测试私人变量的子类调用
cain = Blackcat(5)
cain.get_weight()
# My weight is 7
cain.get_weight_again()
# Actuall my weight is 6
# cain.get_true_weight() # 报错

# 测试方法的继承与覆盖
cain.get_myweight() # 
# My weight is 6 kg
cain.get_trueweight() # 子类中同名函数覆盖父类
# My weight is exactly 7

Tiger

Tiger 和 Whitetiger类中主要讲解super的用法。super用于在子类中调用父类方法及属性,其实很多时候继承时super不是必要的,我们这里只简单了解一下这种用法。

class Tiger:
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def eat(self):
        return "I am eating"
    
    def myname(self):
        return "my name is " + self.name

class Whitetiger(Tiger):
    
    def __init__(self, name, age, height):
        super(Whitetiger, self).__init__(name, age) # 1
        self.height = height

    def eatmore(self):
        return super(Whitetiger, self).eat() + " more" # 2
    
    def realname(self):
        return "Actually " + super(Whitetiger, self).myname()

创建实例

wtony = Whitetiger("Tony", 10, 100)
wtony.eatmore() # 'I am eating more'
wtony.realname() # 'Actually my name is Tony'

上面有两个地方用到了super

  • #2 处用super调用父类的eat方法,但实际上用self.eat调用就可以了
  • #1 处让子类中可以调用父类的属性,其实就相当于运行父类的_init_函数。如果没有#1,则实例无法调用name属性;也无法调用realname方法,因为它用到了name属性。#1处有几种等价定义形式如下

第一种,将父类定义的属性重新定义一遍

def __init__(self, name, age, height):
    self.name = name
    self.age = age
    self.height = height

第二种,不使用super而是直接用父类名称,这样做的弊端是,如果父类名称改了,则所有这么用的位置都得改

def __init__(self, name, age, height):
    Tiger.__init__(self, name, age)
    self.height = height

第三种,如果参数太多,可以用简单的写法

def __init__(self, height, *args, **kwargs):
    super(Whitetiger, self).__init__(*args, **kwargs)
    self.height = height

专栏信息

专栏主页:python编程

专栏目录:目录

版本说明:软件及包版本说明

编辑于 2019-08-24

文章被以下专栏收录