Python3 - 类和实例

1 类的定义

定义一个普通的类:

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
def __init__(self, arg):
# 定义一个实例的私有属性
self.__real_arg = arg

# 为实例的公有方法
def func1(self, arg):
...

# 为实例的私有方法
def __func3(self, arg):
...

有几点需要注意:

  • class Study(object) 中括号里的内容为一个所要继承的类。object 类似于根类,这是所有类最终都会继承的类。
  • __init__() 这个方法相当于是一个构造函数。除此之外,还可以添加一些其他语句,使得在创建实例时,就自动执行。
  • 所有实例方法的第一个参数必须为 self。在创建实例或者调用方法时,self 不需要传,Python解释器自己会把实例变量传进去。
  • 外部创建实例时,要用以下方式:

    1
    s = Student()

对于C++,则为:

1
Student s();
  • 在类的内部,要访问属性或者方法时,必须在相应的属性名或方法名前加上 self.
  • 在外部访问属性或者方法时,必须在相应的属性名或者放方法名前加上 实例名.
  • 对于前边加上 __ 符号的属性名 __name,为类的一个私有的属性变量,原则上 只能在类的内部直接访问,不能在类的外部直接访问。否则,为类的一个公有的属性变量,在类的内部和外部都可以直接访问。不能直接访问__name是因为在程序运行时,翻译器就会自动将其变成_Student__name这个名字,因此,在外部访问这个私有变量,都要用改变之后的名字(比如:s._Student__name)。在内部定义某个方法时,仍然通过self.__name 进行访问。
  • 对于方法,同样如此。
  • 在外面也可以定义某实例的属性,为一个公共属性。但它只属于这一个实例,当再次定义一个同类型的实例时,新定义的这个实例是不会有这个属性的。同样,也可以在外面定义一个实例的方法,甚至是给所有的实例定义一个方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import types

    class Student(object):
    pass

    s = Student()

    # 在外面为实例s定义一个属性
    s.name = 'Jason'

    # 定义一个函数作为实例方法
    def set_age(self, age):
    self.age = age

    # 在外面,将set_age方法作为实例s的一个方法
    s.set_age = types.MethodType(set_age, s)

    # 在外面,给所有的实例(不管是已经被定义过的还是还没定义的)绑定一个方法。
    Student.set_age = MethodType(set_age, Student)

2 继承与多态

Python是一门动态语言,它在继承上和静态语言类似,但在多态上,和静态语言有很大不同。

2.1 继承

继承的通常情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Animal(object):
def __init__(self):
self.__name = "python"

def run(self):
print('Animal is running...')

def get1(self):
print(self.__name)

# 由Animal派生出的一个子类
class Dog(Animal):
def __init__(self):
self.__name = "c++"

# 会覆盖父类的run方法
def run(self):
print('Dog is running...')

def get2(self):
print(self.__name)

# 定义的新方法
def eat(self):
print("Dog is eating...")

# 由Animal派生出的一个子类
class Cat(Animal):
def __init__(self):
self.__name = "java"

# 会覆盖父类的run方法
def run(self):
print('Cat is running...')

def get3(self):
print(self.__name)

# 定义的新方法
def sleep(self):
print("Cat is sleeping...")

dog = Dog()
# Dog is running...
dog.run()

cat = Cat()
# Cat is running...
cat.run()

# 传入到get1方法的第一个参数为dog这个实例,而get1方法中的__name在程序运行时由翻译器改成了_Animal__name。但dog这个实例并没有_Animal__name这个属性。故而,这个语句会发生错误。
dog.get1()
  • 定义子类时,需要在类名后面的括号里,写上父类的名字。
  • 对于上面的例子,一个实例若是Dog类型,那它也是Animal类型。也即所有的Dog类型实例都是Animal类型。
  • 子类拥有父类的所有实例属性,但在子类的 __init__ 函数中一定要显示地地调用父类的 __init__ 方法(通过 super 关键字),否则在初始化子类对象时并不会初始化父类中的实例属性部分)和类属性,以及实例方法和类方法。

    类属性和方法详见后面讲解。

  • 子类可以添加新的属性(实例属性或类属性)和方法(实例方法或类方法)。
  • 当子类拥有与父类同名的属性(实例属性或类属性),或者同名的方法(实例方法或类方法,且不管它们参数是否相同)时,会发生覆盖。
  • 传入到子类自身的实例方法和继承的实例方法参数列表中的self,为子类类型的实例。传入到子类自身的类方法和继承的类方法的参数列表中的第一个参数,为子类类名。
  • 在子类的方法中可以通过 super 来调用父类的方法(尤其是在调用与子类方法同名的父类方法时很有用)。

    super(子类类名, self).父类方法名(参数列表)

2.3 多态

静态语言中的多态,是建立在继承的基础上的,而动态语言的多态,和继承毫无关系。 在动态语言中,只要不同的类具有同名的方法,且参数相同,就可以发生多态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal(object):
def __init__(self):
pass

def func(self):
print("Animal...")

class Dog(object):
def __init__(self):
pass

def func(self):
print("Dog...")

def operation(a):
a.func()

# Dog...
operation(Dog())
# Animal...
operation(Animal())

3 获取实例信息

3.1 type()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# <class 'int'>
print(type(123))

# True
print(type(123) == int)

# <class 'builtin_function_or_method'>
print(type(abs))

# True
print(type(abs)==types.BuiltinFunctionType)

# True
print(type(lambda x: x)==types.LambdaType)

# True
print(type((x for x in range(10)))==types.GeneratorType)

def func():
pass

# True
print(type(func)==types.FunctionType)

上例中,types这个类中定义了很多常量。有types.FunctionType,types.LambdaType,types.GeneratorType,types.MethodType,types.BuiltinFunctionType,types.BuiltinMethodType等。

注意:当一个类是某个类的子类时,用type()判断子类实例的类型时,只为这个子类类型,而不为父类类型。

3.2 isinstance()

isinstance()除了可以判别出type()能判别出的那些类型外,还适用于存在继承关系的类的实例的类型判断。

1
2
3
4
5
# True
print(isinstance(123, int))
# 并且还可以判断一个变量是否是某些类型中的一种
# True。判断[1, 2, 3]是否为list, tuple其中的一种。
print(isinstance([1, 2, 3], (list, tuple)))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal(object):
def __init__(self):
pass

def func(self):
print("Animal...")

class Dog(Animal):
def __init__(self):
pass

def func(self):
print("Dog...")

a = Dog()
# True
print(isinstance(a, Dog))
# True
print(isinstance(a, Animal))

3.3 dir()以及hasattr(),getattr(),setattr()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list。

除此之外,可用hasattr()判断实例是否具有某个属性,可用getattr()获取实例的某个属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal(object):
def __init__(self):
self.__name = "python"

def func(self):
print("Animal...")

class Dog(Animal):
def __init__(self):
self.__name = "c++"

def func(self):
print("Dog...")

a = Dog()
# False
print(hasattr(a, "__name"))
# True
print(hasattr(a, "_Dog__name"))
# False。因为a并没有_Animal__name这个属性
print(hasattr(a, "_Animal__name"))

# c++
print(getattr(a, "_Dog__name"))
# 实例a没有_Animal__name这个属性,默认返回404
print(getattr(a, "_Animal__name", 404))

# 为实例a定义一个公共属性age,值为9
setattr(a, "age", 9)

还可以通过hasattr判断一个实例是否具有某个方法,通过getattr获取一个实例的方法,通过setattr为一个实例添加一个属性。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal(object):
def __init__(self):
self.__name = "python"

def func(self):
print("Animal...")

def sleep(self):
print("Sleeping...")

class Dog(Animal):
def __init__(self):
self.__name = "c++"

def func(self):
print("Dog...")

a = Dog()
# True
print(hasattr(a, "func"))
# True
print(hasattr(a, "sleep"))

# <bound method Dog.sleep of <__main__.Dog object at 0x1017e2be0>>
print(getattr(a, "sleep"))
a_func = getattr(a, "sleep")
# 也即a.sleep()
# Sleeping...
a_func()

4 实例属性和类属性

  • 可以在定义类时定义实例属性(在__init__函数里定义,self.实例属性名 = 值),属于每一个实例。也可以在外部定义实例属性(实例名.实例属性名),属于这一个实例。

  • 类属性可以在定义类时进行定义(若不放在方法中,形式为,类属性名 = 值;若放在方法中,形式为,类名.类属性名 = 值),也可以在类的外部定义(类名.类属性名 = 值)。无论是哪种方式,定义的类属性都是归类所有。

  • 若一个实例的某个实例属性不存在,则获取它所属类的同名类属性。否则,获取它的这个实例属性。

  • 对于类来说,只能获取类属性,而不能获取实例属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Student(object):
# 在定义类时,定义一个类属性name。
name = "Student"

def __init__(self):
# 在定义一个类时,定义一个实例属性。只能放在某一个方法里。一般放在__init__这个方法里。
self.age = 10

# 在外部,定义一个类属性。
Student.grade = 2
s = Student()
# 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性。
# Student
print(s.name)

# 在外部,为实例s定义一个name属性,这个属性为实例属性。
s.name = 'Jason'
# 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
# Jason
print(s.name)
# 但是类属性并未消失,用Student.name仍然可以访问
# Student
print(Student.name)

# 删除实例的name属性
del s.name
# 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
# Student
print(s.name)
# 可以在外部修改类属性值,当然也可以在外部定义类属性。
Student.name = "Teacher"

5 用classmethod装饰器定义类方法

  • 可以通过classmethod装饰器定义类方法时,参数列表第一个参数接收传入的类名。

  • 类方法既可以被类调用,也可以被实例调用。但实例方法是不可以被类调用的。

  • 若某个子类继承定义这个类方法的父类,当这个子类调用这个类方法时,传入的类变量cls是子类类类型,而非父类类类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Animal(object):
age = 1

@classmethod
def func(cls):
# 打印出cls所指向的类的名字。
print(cls.__name__)
cls.age = cls.age + 1

class Dog(Animal):
age = 1


# 用类调用类方法
# Animal
Animal.func()
# 2
print(Animal.age)

a = Animal()
# 用实例调用类方法
# Animal
a.func()
# 3
print(Animal.age)

# 由于子类中也有一个与父类同名的name属性,因而发生覆盖。
# 1
print(Dog.age)

# 子类继承了父类的类方法。且传入到这个类方法中的第一个参数,为子类类名。
# Dog
Dog.func()
# 2
print(Dog.age)

6 用staticmethod装饰器定义静态方法

用staticmethod定义的静态方法和用classmethod定义的类方法很相似。

  • 静态方法定义时,第一个参数不用为self,或者cls,也就是静态方法完全不依赖于定义或继承它的类及其实例。
  • 既可以通过类调用,也可以通过实例调用。
  • 由于静态方法完全不依赖于定义或继承它的类及其实例,故而,对于继承定义静态方法的类一个子类或其实例,当想要定义或修改父类属性时,或者其他一些情况时,就可以用静态方法实现。
  • 其实,静态方法想要实现的功能完全可以由类方法实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal(object):
age = 1

@staticmethod
def func():
# 修改Animal的类属性
Animal.age = Animal.age + 1

class Dog(Animal):
age = 1

Dog.func()
# 子类继承了父类的func方法,虽然子类的类属性age覆盖了父类的类属性,但是上面这语句是对父类的类属性age修改,故而子类的类属性age仍为1。
# 1
print(Dog.age)

用类方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal(object):
age = 1

@classmethod
def func(cls):
# 虽然传了一个类进来,但是只要不使用它就可以了。
Animal.age = Animal.age + 1

class Dog(Animal):
age = 1

Dog.func()
# 1
print(Dog.age)

7 使用property装饰器

使用property装饰器,可以把一个方法变成属性调用。可以定义三种类型的getter,setter,deleter这三种方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Student(object):
@property
def score(self):
return self.value

@score.setter
def score(self, val):
if not isinstance(val, int):
raise ValueError('score must be an integer!')
if val < 0 or val > 100:
raise ValueError('score must between 0 ~ 100!')
self.value = val

@score.deleter
def score(self):
del self.value

s = Student()

# 实际转化为s.set_score(60)
s.score = 60

# 实际转化为s.get_score()
# 60
print(s.score)

# 报错
s.score = 9999

# 删除s的value属性
del s.score

注意:这三个方法的名字一定要相同。

8 枚举类

enum模版中定义了Enum类,以及unique装饰器。Enum类是一个自定义枚举类的基类。unique装饰器用以检查枚举的值是否重复。可以通过定义一个Enum子类的方法,定义一个枚举类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import enum

@enum.unique
class Weekday(enum.Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

# Weekday.Mon
print(Weekday.Mon)

# Weekday.Mon
print(Weekday["Mon"])

# Weekday.Mon
print(Weekday(1))

# 1
print(Weekday.Mon.value)

9 定制类

每个类都有很多个特殊的变量或者方法,可以通过自定义它们,达到一些特殊目的。

9.1 __slots__变量

用以限制定义的实例属性。

如果不定义__slots__变量,那么可以在动态期间,为一个类的实例绑定任何属性。如下所示:

1
2
3
4
5
6
7
8
class Student(object):
pass

s = Student()
s.name = "python"
s.score = 100

print("name: %s, score: %d" % (s.name, s.score))

但是,一旦定义了__slots__变量(一个tuple),那么对于这个类的实例就只能动态定义它所限制的属性了,而不能定义其他属性。如下所示:

1
2
3
4
5
6
7
8
class Student(object):
__slots__ = ("name", "age")

s = Student()
s.name = "python"
s.age = 40
# 会产生错误信息。AttributeError: 'Student' object has no attribute 'score'
s.score = 100

注意:定义的__slots__变量只对这个类的实例其作用,不对它的子类起作用。子类实例允许定义的属性是自身的__slots__加上父类的__slots__。

9.2 _str_()方法

用以定义一个类的实例的描述性说明。

1
2
3
4
5
6
7
8
9
class Student(object):
def __init__(self, name):
self.name = name

def __str__(self):
return "Student object (name: %s)" % self.name

# Student object (name: Python)
print(Student("Python"))

9.3 __iter__()方法与__next__()方法

将一个类的实例变为可迭代类型。

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个_iter_()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1

def __iter__(self):
# 由于这里是要将Fib实例变成一个可迭代对象,因此直接返回这个实例即可。
return self

def __next__(self):
self.a, self.b = self.b, self.a + self.b
# 如果大于20,则抛出错误。但在这里,不会打印出这个错误。
if self.a > 20:
raise StopIteration()
return self.a

for n in Fib():
print(n)

9.4 __getitem__

像list,tuple那样,在一个实例后边加上[下标]来获取某一个元素,以及加上[切片]来获取某几个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a

# slice(切片)是一个类。
if isinstance(n, slice):
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L


f = Fib()
# 2
print(f[2])
# [1, 1, 2, 3, 5]
print(f[0: 5])

9.5 __setitem__

大致用法同__getitem__。

9.6 __delitem__

大致用法同__getitem__。

9.7 __getattr__

获取一个实例的属性。当某个属性不存在时,可以返回一个通过__getattr__定义的这个属性的值。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):

def __init__(self):
self.name = 'Python'

def __getattr__(self, attr):
if attr=='score':
return 99

s = Student()
# 99
print(s.score)

9.8 链式调用

这在REST API中很常见,通过基于__getattr__的链式调用,动态定制URL。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Chain(object):

def __init__(self, path=''):
self._path = path

# 每次取一个原本不存在的属性的时候,都会将当前实例中的_path属性值与新的属性名放在一起,然后再用这个新的值生成并返回一个新的实例。
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))

# 每打印一个实例,事实上即使打印的这个实例的_path属性值。
def __str__(self):
return self._path


# /status/user/timeline/list
print(Chain().status.user.timeline.list)

10 元类

metaclass允许你创建类或者修改类,可以把类看成是metaclass创建出来的“实例”。 (由于一般用不到,先略过。。。)


Reference