Python面向对象(一)

本文介绍了Python面向对象编程的基础概念,包括类的定义、实例化、类变量与实例变量的区别、方法重载与重写等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python也是一门面向对象的编程语言。下面就让我们走进Python的面向对象编程世界。但是在正式进行python的面向对象编程时,得先了解一些Python中类的知识。
类是Python实现支持继承的新种类的对象的部件,也是Python面向对象程序设计(OOP)的主要工具。
PYthon中类的建立使用了一条新的语句:class语句。

1、面向对象有三大特点:

  • 封装
  • 继承
  • 多态

2、什么叫类?

类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。概括的讲,类就是一些函数的包,这些函数大量使用并处理内置对象类型。类的设计也是为了创建和管理对象,并且支持继承,这是一种代码定制和复用的机制。
从具体的程序设计观点来看,类是Python的程序组成单元,就像我们之前说的函数和模块一样,类是封装逻辑和数据的另一种方式。类由三个重要的独到之处,使其在建立新对象时更有用。

  • 多重实例
    类基本上就是产生对象的工厂。每次调用一个类,就会产生一个独立命名空间的新对象。每个由类产生的对象都能读取类的属性,并获得自己的命名空间储存数据,这些对象对于每个对象来说都不同。
  • 通过继承进行定制
    类也支持OOP的继承。我们可以在类的外部重新定义其属性或者增加没有的方法从而扩充这个类。更通用的是,类可以建立命名空间的层次结构,而这种层次结构可以定义该结构中类创建的对象所使用的变量名。所有从类产生的实例都会继承该类的所有属性和方法。
  • 运算符重载
    通过提供特定的协议方法,类可以定义对象来响应在内置类型上的几种运算。例如,通过类创建的对象可以进行切片、级联和索引等运算。Python提供了一些可以由类使用的钩子,从而能够中断并实现任何的内置类型运算。

3、创建类

使用class语句来创建一个新类,class之后为类的名称,括号中为基类(可以有多个基类)并且也是以冒号结尾。

class ClassName(BaseClass, ...) :
  'optional class documentation string' #类文档字符串
  class_suite  #类体

class_suite由类成员,方法,数据属性组成。
类的帮助信息可以通过ClassName.__doc__查看。
类定义实例:

#!/usr/bin/python
#coding=utf-8

class Employee (object):
  #所有员工基类
  empCount = 0

  def __init__(self, name, salary) :
    #类的构造函数
    self.name = name
    self.salary = salary
    Employee.empCount += 1

  def displayCount(self) :
    #类方法
    print "total employee ",Employee.empCount

  def displayEmployee(self) :
    print "name :",self.name , ", salary :", self.salary

说明:
empCount变量是一个类变量(也叫静态变量),它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用Employee.empCount访问。

第一个中方法init()是一种特殊方法,被用做类的构造函数或初始化方法,只要创建类的实例时,就会调用这个方法。如果没显示定义这个方法,默认会给一个空的方法。

类方法中参数中的self,代表实例本身,相当于java中的this指针。并且类中所有的方法中都必须有self,并且写在第一个参数位置。

所有类都是继承基类object。

4、创建实例对象

要创建一个类的实例,可以使用类的名称,并通过__init__方法接受参数。
创建一个类的具体对象。每次从类产生实例时,Python都会自动调用名为__init__的方法,也叫类的构造方法,进行数据初始化。新实例会如往常那样传入__init__的self参数。该方法也会在类中被继承。
如给上面的类创建实例:

emp1 = Employee("joy", 10000)
emp2 = Employee("sr", 10000)

如果所定义的类中没有要初始化的参数时,创建实例时,就不需要传入参数。

5、类变量

Python中类变量也叫类的静态成员变量或类属性,类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体外。类变量通常不作为实例变量使用,它的值在这个类的所有实例之间共享。
类变量紧接在类名后面定义,相当于java和c++的static变量
实例变量在__init__里定义,相当于java和c++的普通变量

class Parent(object): # define parent class 
  parentAttr = 100 
  def __init__(self): 
    print "Calling parent constructor"

parentAttr变量就是一个类变量。

看一个关于类变量访问的例子:

class f(object) :
  count = 12
t = f()
print "t.count内存地址 =", id(t.count), "t.count =", t.count
print "f.count内存地址 =", id(f.count), "f.count =", f.count
t.count = 10
print "通过实例变量修改count的值后"
print "t.count内存地址 =", id(t.count), "t.count =", t.count
print "f.count内存地址 =", id(f.count), "f.count =", f.count

执行结果:

t.count内存地址 = 30176864 t.count = 12
f.count内存地址 = 30176864 f.count = 12
通过实例变量修改count的值后
t.count内存地址 = 30176912 t.count = 10
f.count内存地址 = 30176864 f.count = 12

从上例可以看出,类变量既可以通过类名直接调用,也可以通过实例对象调用。
当我们运行下面这句后:

t.count = 10

再次打印t.count的值没发现已经改变成10,而f.count的值仍然是12,这是为什么呢?
Python可以在类外面给类添加类属性,但是这种添加如果是直接添加到类中的,也就是用类名.属性名 = value这种方式添加的,那这个属性就是类的类属性,其所有的实例对象都可以访问。但是如果是通过类的某个实例对象去添加的,就像 实例名.属性名 = value,这种添加方式,那这个属性只属于这个实例对象所有,并且如果所添加的属性名与类中类属性名相同的时,类属性在这个实例对象中就会被屏蔽掉,也就是说实例引用时,同名的实例属性会屏蔽同名的类属性,就类似于全局变量与局部变量的关系,所以此时再打印t.count,就是引用的是t实例中的count属性(除非删除该实例变量)。这就是为什么上例执行t.count = 10以后,t.count的值变成了10的原因。

下面是一个具体的实例:

class f(object) :
  count = 12

t = f()#创建类的实例对象
print t.count
print f.count
t.count = 10
print "****添加一个同名的实例count属性后****"
print t.count
print f.count
print "****添加一个类属性后****"
f.age = 34
print f.age
print t.age
print "****给实例对象添加一个新的属性后****"
t.address = '北京'
print t.address
print f.address

执行结果:

12
12
****添加一个同名的实例count属性后****
10
12
****添加一个类属性后****
34
34
北京
Traceback (most recent call last):
  File "C:\Users\Administrator\Desktop\JavaTest\11-4\test.py", line 30, in <module>
    print f.address
AttributeError: type object 'f' has no attribute 'address'

上面实例需要注意:
用类名访问实例中的属性时,会报AttributeError错误。

再看另一个实例:

class Parent(object):
    x = 1
class Child1(Parent):
    pass
class Child2(Parent):
    pass

print Parent.x, Child1.x, Child2.x # 1 1 1
print id(Parent.x), id(Child1.x), id(Child2.x)
Child1.x = 2
print Parent.x, Child1.x, Child2.x #1 2 1
print id(Parent.x), id(Child1.x), id(Child2.x)
Parent.x = 3
print Parent.x, Child1.x, Child2.x # 3 2 3
print id(Parent.x), id(Child1.x), id(Child2.x)

执行结果:

1 1 1
30900456 30900456 30900456
1 2 1
30900456 30900432 30900456
3 2 3
30900408 30900432 30900408

从实例看出当子类继承了父类的类属性以后,如果不在子类中对其改变,其实质也是给子类新创建了一个同名的类属性,父类的该类属性值并未改变。

说了这么多,那到底怎么才能在类外改变类属性的值呢?
通过类方法可以改变类属性的值,这个讲类方法是再讲吧。

6、实例变量

实例变量在__init__里定义,相当于java和c++的普通变量,只作用于当前类的实例。实例变量前面需要加上self关键字。如:

class Person(object):
    id = 12   #类静态成员在这儿定义,注意缩进
    def  __init__(self,name):
        self.name = name #类的实例变量
        self.__inName = 'ads' #类的实例变量

而在类的实例方法中定义的变量,叫局部变量。

7、类方法、静态方法和实例方法

类方法和静态方法很像,在调用时,都不需要产生类的实例去调,而是可以直接使用类名去调。
但是它们也是有区别的:类方法传参时需要把类本身的实例(cls,指向该类本身)传进入,而静态方法则不需要传任何实例,只有普通参数。
类方法是类对象所拥有的方法,需要用修饰器”@classmethod”来标识其为类方法,而静态方法需要使用修饰器”@staticmethod”来进行修饰,并且静态方法不需要定义参数。

class Person(object):
    id = 12   #类静态成员在这儿定义,注意缩进
    def  __init__(self,name):
        self.name = name
        self.__inName = 'ads'

    @classmethod   #类方法定义,用注解方式说明
    def up( cls,a ):   #这儿是cls,不是self
        print cls,cls.__name__
        return a+1

    @staticmethod    #类静态方法定义,用注解方式说明
    def down( b):   #静态方法不需要self,cls变量
        id = 13
        return b-1

调用:

print Person.up(20)
print Person.down(15)

使用类方法修改类属性:

class employee(object) : 
  city = 'BJ'

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

  @classmethod
  def getCity(cls) :
    return cls.city

  def set_City(self, city) :
    employee.city = city

  @classmethod
  def setCity(cls, city) :
    cls.city = city

emp = employee('joy')
print emp.getCity() #通过实例对象引用类方法
print employee.getCity()#通过类对象引用类方法

emp.setCity('TJ')#实例对象调用类方法改变类属性的值
print emp.getCity()
print employee.getCity()

employee.setCity('CD')#类对象调用类方法改变类属性的值
print emp.getCity()
print employee.getCity()

emp.set_City('SH')#调用实例方法改变类属性的值
print emp.getCity()
print employee.getCity()

employee.city = 20 #直接修改类属性的值
print emp.getCity()
print employee.getCity()

执行结果:

BJ
BJ
TJ
TJ
CD
CD
SH
SH
20
20

通过上面设置的修改类属性的类方法,既可以通过类对象直接调用,也可以在实例对象中调用,这样修改的类属性的值在所有实例对象间数据都是同步的。

下面是一个静态方法的实例:

class test(object) :
  name = "cndy"
  @staticmethod
  def getName() :
    return test.name

t = test()
print t.getName()
print test.getName()

可见,静态方法既可以通过类对象直接调用,也可以通过实例对象调用。

下面是一个关于实例方法的例子:

class car(object) :
  color = 'red'
  def __init__(self, name, brand) :
    self.name = name
    self.brand = brand

  def getName(self) ;’
    return self.name

c = car('d10', 'dazong')
print c.getName()

上面的getName方法就是实例方法,需要注意的是,实例方法只能通过实例对象去调用。

8、方法重载

有时候,我们可能需要在同一个类中定义几个功能类似但参数不同的方法。
方法的重载(overloading)是允许在一个类的定义中,多个方法使用相同的方法名。
如果你学过C++、JAVA或者是其他面向对象编程语言,你会发现Python中的重载与其他面向对象编程语言在使用上是有区别,其实可以这么说Python在形式上是不支持重载。下面先以JAVA为例,讲解一下方法重载。
重载是指在同一类中一个方法名被用来定义多个方法。
方法的重载是面向对象编程语言多态性的一种形式,它实现了java的编译时多态,即由编译器在编译时刻确定具体调用哪个被重载的方法。函数重载大大提高了代码重用率和程序员开发效率。
重载方法的名称是不同的,但在方法的声明中一定要有彼此不相同的成份,以使编译器能够区分这些方法。因此JAVA中规定重载的方法必须遵循下列原则:

  • 方法的参数表必须不同,包括参数的类型或个数,以此区分不同方法体;
  • 方法的返回类型、修饰符可以相同,也可以不同。
public void println()
public void println(char x)
public void println(char x, int x)

(1).Python方法重载
而对于Python来说,虽然它形式上不支持函数重载。但Python也是一门面向对象语言,自然也不会丢弃函数重载这特性。Python中实现函数重载的方法非常特别。首先,Python本身是动态语言,方法的参数是没有类型的,当调用传值的时候才能确定参数的类型,所以想通过参数类型的不同来实现函数重载是不可行了。那到底怎么实现Python的重载呢?
参数类型行不通了,那我们考虑通过参数数量来试试。Python中针对函数参数个数有一个很好使的功能就是缺省参数。
利用重载机制可以进行如下定义:

def println(str1, time = 1) :
  print str1 * time

通过设置缺省参数,就可以实现Python中的函数重载。

(2).Python运算符重载(重写)
在Python中,运算符重载的方式很简单,每个类都会有默认内置一些运算符方法,只要重写这个方法,就可以实现针对该运算符的重载。例如下面是重载加法操作:

class Vector(object) :
  def __init__(self, a, b) :
    self.a = a
    self.b = b
  def __str__(self): 
    return 'Vector (%d, %d)' % (self.a, self.b)
  def __add__(self,other) :
    return Vector(self.a + other.a, self.b + other.b)

x =  Vector(3,7)
y =  Vector(1, -10)
print x + y

执行结果:

Vector (4, -3)

上面的实例重写了类的加法操作,你也可以重写其他的方法。

9、方法重写

在类层次结构中,当子类的成员变量与父类的成员变量同名时,子类的成员变量会隐藏父类的成员变量;当子类的方法与父类具有相同的名字、参数列表、返回值类型时,子类的方法重写(overriding)了父类的方法,在父类定义的方法就被隐藏。“隐藏”的含义是,通过子类对象调用子类中与父类同名的变量和方法时,操作的是这些变量和方法是在子类中的定义。子类通过成员变量的隐藏和方法的重写可以把父类的状态和行为改成为自身的状态和行为。
重写是指子类重写父类的成员方法。子类可以改成父类方法所实现的功能,但子类中重写的方法必须与父类中对应的方法具有相同的返回值、方法名和参数列表。也就是说要实现重写,就必须存在继承。
方法重写必须遵守的原则:

  • 子类中重写方法的返回值类型必须与父类中被重写方法的返回值类型相同。
  • 子类中重写方法的访问权限不能缩小。
  • 子类中重写方法不能抛出新的异常。

方法重写是现实对象运行时多态的基础。
形状类:

class Shape(object):
  def __init__(self,x):
    self.x = x
    self.rx = 10
  def area(self,x):    
    pass

圆类(继承形状类):

class Circle(Shape):
  def __init__(self,x):
    #super(Circle,self).__init__(x)
    #Shape.__init__(self,x)
    pass
  def area(self,x):
    print 'in circle'
    return 3.14 * x ** 2

圆类继承了形状类,并且重写了父类的构造方法和求面积的方法。并且在子类重写父类构造方法时,以父类方法作为一个基础,子类中只是对其做一个扩展,这个时候就会要调用父类的构造方法时,有三种方法:

方法一:(比较灵活)
super(Circle,self).__init__(x)
方法二:
Shape.__init__(self,x)
方法三:
子类不定义自己的构造方法时,默认就会调用父类的构造方法。

注意:
super只能用于构造方法中。

如果你的父类不能满足你的需求,你就可以在子类中重写父类的方法,常见重写的父类的方法有:

1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值