Skip to main content

面向对象

  • **类(Class): **用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法: 类中定义的函数。
  • 类变量: 类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员: 类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写: 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量: 定义在方法中的变量,只作用于当前实例的类。
  • 实例变量: 在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承: 即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化: 创建一个类的实例,类的具体对象。
  • 对象: 通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
class MyClass:
"""一个简单的类实例"""
i = 12345
def f(self):
return 'hello world'

# 实例化类
x = MyClass()

# 访问类的属性和方法
print("MyClass 类的属性 i 为:", x.i)
print("MyClass 类的方法 f 输出为:", x.f())

类属性与方法

类的成员包括属性(attribute)与方法(method)两种。例子:

class MyClass:
"""
This is a class that can meow!
"""
animal = "cat" # An attribute
def talk(self): # A method
return "Meow"

# An instance of the class
a = MyClass()
print(a.animal, a.talk())
cat Meow

上例中的 self 表示类的实例,所有类内部的方法都需要把该参数放在首位(你也不可不用 self 而使用 this 等,但是 self 是惯例)。例如,self.animal 就表示了实例的 animal 属性。这与 C# 等语言中的“this.animal”是类似的。

下例证明了 self 代表的实质是类的实例,而不是类本身。

class EgClass:
def __init__(self):
print(self) # 实例,有对应地址
print(self.__class__) # 类

a = EgClass()
<__main__.EgClass object at 0x000002531C0AF860>
<class '__main__.EgClass'>

类的私有属性和方法

__private_method :两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。

类的私有属性格式与私有方法相同

方法重写

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法

class Parent:        # 定义父类
def myMethod(self):
print ('调用父类方法')

class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')

c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
#结果
调用子类方法
调用父类方法

构造函数:__init__()

类的构造函数是 __init__() (左右均为双下划线),用于初始化实例。在声明实例(实例化)时,该函数自动被调用。

class MyClass2:
def __init__(self, animal="cat"):
self.animal = animal

a = MyClass2("dog")
a.animal
'dog'

self代表类的实例,而非类

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的 第一个参数名称 , 按照惯例它的名称是 self。

class Test:
def prt(self):
print(self)
print(self.__class__)

t = Test()
t.prt()
<__main__.Test instance at 0x100771878>
__main__.Test
#self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类

类的静态方法、普通方法、类方法

静态方法 : 用 @staticmethod 装饰的不带 self 参数的方法叫做静态方法,类的静态方法可以没有参数,可以直接使用类名调用。

普通方法 : 默认有个self参数,且只能被对象调用。

类方法 : 默认有个 cls 参数,可以被类和对象调用,需要加上 @classmethod 装饰器。

class Classname:
@staticmethod
def fun():
print('静态方法')

@classmethod
def a(cls):
print('类方法')

# 普通方法
def b(self):
print('普通方法')

封装

类的重要特性是封装性,即部分变量只能在其内部修改或访问,不能从类的外部进行处理。Python 中的封装非常简单,只要把属性或方法的名称前缀设置为双下划线即可。

由此可见,构造函数 __init__() 是最基本的一个私有方法。一个例子:

class MyClass3:
def __init__(self, animal="cat"):
self.__animal = animal
self.__foo()
def __foo(self):
self.__animal = "rabbit"
def show(self):
print(self.__animal)

a = MyClass3("dog")
a.show()
rabbit

如果想直接调用 __foo() 或者 __animal,都会被禁止,产生 AttributeError

# a.__animal  # AttributeError

要注意,前后均添加了双下划线的属性,如 name ,表示特殊属性而不是私有属性,是可以从外部访问的。

继承

子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。

下面是一个著名的猫与狗的例子;类 Cat 与 Dog 都继承自 Animal,同时也都重载了方法 talk()。

class Animal:
def talk(self):
pass # 表示定义留空

class Cat(Animal): # 从Animal 继承
def talk(self): # 重写talk()
print('Meow')

class Dog(Animal):
def talk(self):
print('Woof')

a, b = Cat(), Dog()
a.talk() # 'Meow'
b.talk() # 'Woof'
Meow
Woof

通过 isinstance() 函数可以判断一个对象是否是某个类(或其子类)的实例:

print(isinstance(a, Cat), isinstance(a, Animal))
True True
或者:

type(a).__name__
'Cat'

当然,类也可以多继承。写在左侧的类的属性与方法,在继承时会被优先采用。例如:

class Pet:
def talk(self):
print("Pet")

class Cat2(Pet, Cat):
pass

a = Cat2()
a.talk()
Pet

@property 装饰器

装饰器 @property 可以被用于限制类属性的读写行为。比如,一个普通的类,如果想封装一个属性,却允许从外部读取它的值,一般我们用 getter 函数实现:

class Person:
def __init__(self):
self.__name = "Py"
def get_name(self):
return self.__name
a = Person()
a.get_name()
'Py'

不得不说这实在是麻烦了,代码里一堆 get 函数满天飞并不令人愉快。而且还不能忘记它是一个函数,需要在尾部加上括号。

装饰器 @property 可以将一个方法伪装成同名的属性,因此装饰了 getter 函数后,调用时就不用加上尾部的括号了:

class Person:
def __init__(self):
self.__name = "Py"

@property #相當於getter方法
def name(self):
return self.__name
a = Person()
a.name
'Py'

而且,如果你想从外部修改该属性的值,会产生错误:

a.name = 1
---------------------------------------------------------------------------

AttributeError Traceback (most recent call last)

<ipython-input-97-8c607f2aa25b> in <module>()
----> 1 a.name = 1


AttributeError: can't set attribute

但同时,我们也可以指定其 setter 函数(该装饰器 @age.setter 在用 @property 装饰 age 方法后会自动生成),让属性修改成为可能,甚至附加修改条件:

class Person:
def __init__(self):
self.__age = 20

@property
def age(self):
return self.__age

@age.setter
def age(self, value):
if not isinstance(value, int):
raise ValueError("Age should be an integer.")
else:
self.__age = value

a = Person()
a.age = 30
a.age
30

不传入整数会报错:

a.age = 0.5
---------------------------------------------------------------------------

ValueError Traceback (most recent call last)

<ipython-input-100-001bfa8fe26b> in <module>()
----> 1 a.age = 0.5


<ipython-input-98-83364d5faa13> in age(self, value)
10 def age(self, value):
11 if not isinstance(value, int):
---> 12 raise ValueError("Age should be an integer.")
13 else:
14 self.__age = value


ValueError: Age should be an integer.

类的特殊属性与方法

属性 __dict__

首先是 __dict__属性,用于查看类的属性与方法,返回一个字典。

a = MyClass()
MyClass.__dict__
mappingproxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>,
'__doc__': '\n This is a class that can meow!\n ',
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>,
'animal': 'cat',
'talk': <function __main__.MyClass.talk>})

需要注意的是,此时实例 a 的属性没有被更改过,实例的 dict 是一个空字典:

print(a.__dict__, a.animal)
{} cat

类的 __dict__ 方法下的同名键,与实例具有相同值。

MyClass.__dict__["animal"]
'cat'

一旦被从外部更改,实例 a 的 dict 字典就不再为空。

a.animal = "dog"
print(a.__dict__, a.animal)
{'animal': 'dog'} dog

属性 __slots__

从上面可以看到,非私有的类属性可以从外部更改值,而且属性还能直接从外部增加。slots 属性的作用就在于使类的属性不能从外部进行更改、追加。它能够限制属性滥用,并在优化内存上也有意义。

class MySlotClass():
__slots__ = ("meow", "woof")
def __init__(self):
self.meow = "Meow"
self.woof = "Woof"

a = MySlotClass()
MySlotClass.__dict__
mappingproxy({'__doc__': None,
'__init__': <function __main__.MySlotClass.__init__>,
'__module__': '__main__',
'__slots__': ('meow', 'woof'),
'meow': <member 'meow' of 'MySlotClass' objects>,
'woof': <member 'woof' of 'MySlotClass' objects>})

此时,如果使用 a.__dict__,结果不会返回空字典,而是会报错。

运算符重载

Python 提供了运算符重载的功能。

class Vector:
def __init__(self, a, b):
self.a = a
self.b = b

def __add__(self, another):
if isinstance(another, Vector):
c, d = another.a, another.b
else:
c, d = another, another
return Vector(self.a + c, self.b + d)

def __radd__(self, another):
return self.__add__(another)

def __neg__(self):
return Vector(-self.a, -self.b)

def __sub__(self, another):
return self.__add__(-another)

def __str__(self):
return "Vector({},{})".format(self.a, self.b)

v1 = Vector(0,3)
v2 = Vector(5,-2)
print(v1 - 1, -v2, v1 + v2, v1 - v2)
Vector(-1,2) Vector(-5,2) Vector(5,1) Vector(-5,5)

其中,__repr__()__str__() 的主要区别在于,前者在交互式步骤中显示结果,后者在 print 函数中显示结果。

例如上例,如果直接输入 v1,不会以 “Vector(0,3)”的形式显示。

v1  # 在类中附加定义: __repr__ = __str__ 即可解决问题。
<__main__.Vector at 0x2531c129c88>

迭代行为

在类中也能定义迭代行为,需要 iter () 与 next () 方法。

# 该例改编自官方文档
class MyClass4:
def __init__(self, lst):
self.data = lst
self.__index = len(lst)
def __iter__(self):
return self
def __next__(self):
if self.__index == 0:
raise StopIteration
self.__index -= 1
return self.data[self.__index]

a = MyClass4("Meow")
for char in a:
print(char)
w
o
e
M