[Python] 08 - Classes --> Objects

故事背景


一、阶级关系

1. Programs are composed of modules.
2. Modules contain statements.
3. Statements contain expressions.
4. Expressions create and process objects.

 

 

二、教学大纲 

  • <Think Python>

 

  • 菜鸟教程

Goto: http://www.runoob.com/python3/python3-class.html

参考资源:廖雪峰,Python面向对象编程

参考资源:廖雪峰,Python面向对象高级编程

参考资源:错误、调试和测试

 

  • 面向对象的三大特点

数据封装(Encapsulation )、继承(inheritance )和多态(polymorphism)。

 

 

 

 

Encapsulation


类的定义

  • 构造方法

赋值的的过程,就自动完成了类内的变量定义。

#!/usr/bin/python3
 
class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart
x
= Complex(3.0, -4.5) print(x.r, x.i) # 输出结果:3.0 -4.5

 

self 参数

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的 第一个参数名称:self

注意:self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。

self 记录了 “类的地址”,“类的属性” 等各种类的属性。

class Test:
    def prt(self):
        print(self)        # address
        print(self.__class__)  # name
 
t = Test()
t.prt()

执行结果:

<__main__.Test instance at 0x100771878>
__main__.Test

 

 

权限控制

  • “私有” 属性 & 方法

加上 “双下划线” 定义属性为私有属性。

#!/usr/bin/python3
class people:
  name = ''
  age  = 0
  __weight = 0

def __init__(self, n, a, w):
  self.name = n
  self.age  = a
  self.__weight= w

def speak(self):
  print("weight is {}".format(self.__weight))

def __talk(self):
  print("age is {}".format(self.age))


p = people('runoob', 10, 30)


#####################
# private attribute
#####################

print(p.name)
# print(p.__weight) # <-- this is err case.
print(p._people__weight)


#####################
# private function
#####################

p.speak()
# p.__talk() # <-- this is err case.
p._people__talk()

 

“私有化” 原理

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name
'Bart Simpson'

但是强烈建议你不要这么干,因为 “不同版本的Python解释器可能会把__name改成不同的变量名”

总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

  

  • 限制滥用 “对象成员”  

__slot__关键字

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

注意:使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

>>> s = Student()      # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25         # 绑定属性'age'

>>> s.score = 99       # <---- 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

 

为何用?内存环保

Ref: 10. __slots__ Magic

动态语言意味着“浪费空间”换取“灵活性”。显然,减少不必要的灵活性可以 reduce memory of RAM。添加__slots__后带来的内存环保效果如下:

The code will reduce the burden on your RAM. Some people have seen almost 40% to 50% reduction in RAM usage by using this technique. 

class MyClass(object):
    __slots__ = ['name', 'identifier']
  def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()
    # ...

 

  • 成员变量 Set & Get

过去的策略

分别定义set, get函数操作。

装饰器策略

将函数当做属性来使用:

(a) 只有@property表示只读。

(b) 同时有@property和@x.setter表示可读可写。

(c) 同时有@property和@x.setter和@x.deleter表示可读可写可删除。

 

class student(object):
    
    def __init__(self,id):  
        self.__id=id  
        
    @property      # 只读  
    def score(self):  
        return self._score 
    
    @score.setter  # 只写  
    def score(self, value):        
        # 设置前的checker
        if not isinstance(value,int):  
            raise ValueError('score must be an integer!')    
        if value<0 or value>100:  
            raise ValueError('score must between 0 and 100')  
        # 开始设置 
        self._score=value  
               
    @property      # 只读
    def get_id(self):  
        return self.__id  

s=student('123456')  

s.score=100       #
print(s.score)     #
print(s.__dict__)
print (s.get_id)   # 只读
#s.get_id=456   #只能读,不可写: AttributeError: can't set attribute
经典方式
# 相较于”经典方式“,以下是新式简便方式,新式类(继承自object类)
class A(object):
    
    def __init__(self):
        self.__name=None
        
    def getName(self):
        return self.__name
    
    def setName(self,value):
        self.__name=value
        
    def delName(self):
        del self.__name
        
    name=property(getName, setName, delName)
 
a=A()
print(a.name)   #
a.name='python' #
print(a.name)   #
del a.name      # 删除

 

 

类方法

  • @staticmethod 与 @classmethod

类方法的 3种定义方式

尤其是第一种,比较tricky。 

class Person(object):
    
    # [第壹种方法] 不加任何参数直接定义,也是类方法 
    def Work(): 
        print(" I am working!") 

# [第贰种方法] 加装饰器方法 @classmethod def Think(cls, b):     # 类方法Think必须要带至少1个参数,第一个参数默认为类名,后面可以引用。 cls.Eat(b)     # 在类方法Think中,调用类方法Eat类方法。 cls.Work()     # 在类方法Think中,调用Work类方法。 print(b,",I am Thinking!")
# [第叁种方法] 先定义类方法,至少1个参数,第一个默认为类名 def Eat(cls, b): print(b+",I am eating") Eat=classmethod(Eat)    # 通过内建函数classmethod()来创建类方法。

 

类的三种方法

如下展示了三种方法:普通类成员函数、静态方法、类方法。

#
# class one.
#
class Person(object):
    
    grade=6
    
    def __init__(self):
        self.name = "king"
        self.age=20

    # -----------------------------------------------------------------------    

    def sayHi(self):
        print ('Hello, your name is?',self.name)
        
    def sayAge(self):
        print( 'My age is %d years old.'%self.age)
        
    # -----------------------------------------------------------------------    

    @staticmethod     # 静态方法
    def sayName():
        print ("<staticmethod> my name is king") 

    # -----------------------------------------------------------------------    

    @classmethod      # 类方法
    def classMethod(cls):  
        print('<classMethod> class grade:',cls.grade)        

该类的用法如下:

p = Person()                    # 实例化对象
print('p.grade:',p.grade)       # 实例对象调用类变量

p.grade=9
# 类和对象都可以调用 @classmethod,但这里类的grade不会改变,体现了独立性 p.classMethod() Person().classMethod()
# __init__的变量不属于类
# 但对象找不到变量,就会去找类里的
Person.age Person().age Person.
__dict__ Person().__dict__
# 概念辨析:类对象、实例对象 Person().grade   # 类对象调用类变量 p.sayHi()   # 实例对象调用类成员函数 Person().sayAge()   # 类对象调用类成员函数 # 静态变量任意调用 m=Person() m.sayName()   # 多个实例皆可共享此静态方法 Person().sayName()   # 类对象调用静态方法

 

继承区别

子类的实例继承了父类的static_method静态方法,调用该方法,还是调用的父类的方法和类属性。

子类的实例继承了父类的class_method类方法,调用该方法,调用的是子类的方法和子类的类属性。

Ref: python类的实例方法、静态方法和类方法区别及其应用场景

class Foo(object):
    X = 1
    Y = 14

    @staticmethod
    def averag(*mixes):  # "父类中的静态方法"
        return sum(mixes) / len(mixes)

    @staticmethod
    def static_method():  # "父类中的静态方法"
        print "父类中的静态方法"
        return Foo.averag(Foo.X, Foo.Y)

    @classmethod
    def class_method(cls):  # 父类中的类方法
        print "父类中的类方法"
        return cls.averag(cls.X, cls.Y)


class Son(Foo):
    X = 3
    Y = 5

    @staticmethod
    def averag(*mixes):  # "子类中重载了父类的静态方法"
        print "子类中重载了父类的静态方法"
        print "666 ",mixes
        return sum(mixes) / 3

p = Son()
print "result of p.averag(1,5)"
print (p.averag(1,5))
print "result of p.static_method()"
print(p.static_method())
print "result of p.class_method()"
print(p.class_method())
code mode

 

  • cls参数 - Factory method

构造函数的重载

Ref: class method vs static method in Python 

We generally use class method to create factory methods. Factory methods return class object ( similar to a constructor ) for different use cases.

We generally use static methods to create utility functions.

为何这里提到了”工厂方法“?因为工厂方法需要返回类,而类方法是'天生的”具备cls参数。

实例中通过两种策略返回类,等价于:不用“函数重载”,实现多个版本的”构建函数“

Goto: 为什么 Python 不支持函数重载?而其他语言大都支持?

#############################
# cls: factory method
#############################

from datetime import date
class Person:

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

    @classmethod
    def fromBirthYear(cls, name, birthYear):
        return cls(name, date.today().year - birthYear)

    # 模拟上面的类方法,但第一个参数在使用时,省不掉
def
fromBirthYear2(cls, name, birthYear): return cls(name, date.today().year - birthYear)
    #---------------------------------------------------
def display(self): print(self.name + "'s age is: " + str(self.age)) # (1) 常规方式返回一个实例对象 person = Person('Adam', 19) person.display() # (2) 类方法返回一个实例对象 person1 = Person.fromBirthYear('John', 1985) person1.display()

# (3) 模拟类方法的使用,只是不美观而已
person2 = Person.fromBirthYear2(Person, 'Jeff', 1987)
person2.display()

 

Python实现工厂方法

Goto: [Design Patterns] 01. Creational Patterns - Abstract Factory

重要区别:"抽象工厂"的工厂是类;"工厂方法"的工厂是方法。

 

 

类的字典属性 

  • 查询 “对象” de 属性&方法

>>> # var public >>> hasattr(p, 'name')
True
>>> # var private>>> hasattr(p, '__weight') False >>> hasattr(p, '_people__weight') True
>>> # func public>>> hasattr(p, 'speak') True >>> fnSpeak = getattr(p, 'speak') # <-- func pointer. >>> fnSpeak() weight is 30
>>> # func private>>> hasattr(p, '__talk') False
>>> getattr(p, '__talk', 404)  # 如果不存在,返回默认值404
404
>>> hasattr(p, '_people__talk') True >>> fnTalk = getattr(p, '_people__talk')  # <-- func pointer >>> fnTalk() age is 10

  

查询 “类” de 属性&方法

类貌似不能直接调用方法。因为方法需要self,而 self是实例化后成为对象,才会有的东西

对象属性和类属性虽然不是一个东西,但类的属性会成为对象的 ”备份“,如下所示。

>>> class Student(object):
...     name = 'Student'

>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student

>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student

>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

 

  • 设置 “对象” de 属性&方法

这里,主要关注 class 与 object 之间的趣味关系。

实例化后,两者就操作独立了。

>>> getattr(people, 'age')
0
>>> p = people('p hao', 20, 100) >>> p2 = people('p2 hao', 30, 200) >>> p.age 20 >>> p2.age 30
>>> setattr(people, 'age', 88)
>>> getattr(people, 'age') 88 >>> p.age 20 >>> p2.age 30
>>> delattr(people, 'age') >>> hasattr(people, 'age') False >>> getattr(people, 'age') Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'people' has no attribute 'age'
>>> p.age 20 >>> p2.age 30

  

  • “字典属性” 原理

如果我们在Class没有定义这3个方法,那么系统会用Python自带的内部函数;

如果我们在类里面自定义了这3个方法,那么python会先调用我们自己定义的这3个函数。

self.__dict__

Ref: 到底干了什么?

这里要小心“无限循环”!需要通过最为本质的操作方法:__dict__操作。

class Cat:

    class_level = '贵族'

    def __init__(self,name,type,speed,age):
        self.name  = name
        self.type  = type
        self.speed = speed
        self.age   = age

    def run(self):
        print('%s岁的%s%s正在以%s的速度奔跑' % (self.age, self.type, self.name, self.speed))

def __getattr__(self, item): print('你找的属性不存在') def __setattr__(self, key, value): print('你在设置属性') # self.key=value   # !!! 这种方法不行,会产生无限递归了,因为他本身self.key=value也会触发__setattr__ self.__dict__[key] = value def __delattr__(self, item): print('你在删除属性') # del self.item   # !!! 无限递归了,和上面的__setattr__原理一样 self.__dict__.pop(item) xiaohua = Cat('小花','波斯猫','10m/s',10)

可见,obj 中没有给出函数相关信息。

>>> people.__dict__
mappingproxy({'__module__': '__main__', 'name': '', 'age': 0, '_people__weight': 0, '__init__': <function people.__init__ at 0x7f1659e8b488>, 'speak': <function people.speak at 0x7f1659e8b510>, '_people__talk': <function people.__talk at 0x7f1659e8b598>, '__dict__': <attribute '__dict__' of 'people' objects>, '__weakref__': <attribute '__weakref__' of 'people' objects>, '__doc__': None})

>>> p3 = people('p hao', 20, 100)
>>> p3.__dict__
{'name': 'p hao', 'age': 20, '_people__weight': 100}

 

常见字典属性

(1) 简单的若干属性

类的专有方法:

__init__ : 构造函数,在生成对象时调用
__del__  : 析构函数,释放对象时使用
__repr__ : 打印,转换 __setitem__ : 按照索引赋值 __getitem__ : 按照索引获取值 __len__ : 获得长度 __cmp__ : 比较运算 __call__: 函数调用
__add__ : 加运算 __sub__ : 减运算 __mul__ : 乘运算 __div__ : 除运算 __mod__ : 求余运算 __pow__ : 乘方

其他generic的方法:

'__module__': '__main__',  # 当导入到其他的脚本文件的时候,此时__name__的名字其实是导入模块的名字,不是’__main__’, main代码里面的就不执行了。
'__dict__': <attribute '__dict__' of 'people' objects>, '__weakref__': <attribute '__weakref__' of 'people' objects>, '__doc__': None})

 

(2) __str__, __repr__ 的区别

Ref: 廖雪峰 - 定制类

__repr__() 方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+object at+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。

 

(a) 两者的笼统区别在于:__str__,用户友好;__repr__,程序员友好

print('hello'.__str__())
hello

print('hello'.__repr__())
'hello'


from datetime import datetime as dt

print(dt.today().__str__())
2018-11-30 14:26:38.881492  # 返回的是处理后的str数据

print(dt.today().__repr__())
datetime.datetime(2018, 11, 30, 14, 26, 48, 580276) # 返回的是原始函数调用的数据方法

 

(b) 为什么要用?打印类的效果更为美观

class Chain(object):

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

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))

    def __str__(self):
        return self._path

    __repr__ = __str__



c = Chain('hello world')
print(c) # call __str__
hello world

c # call __repr__
hello world

 

(c) 进一步配合使用实现:链式调用

# (1) try to find this attribute --> __getattr__
# (2) go to the next Chain by 'return', and print attribute name --> __repr__
Chain().status.user.timeline.list

 

(3) __call__ 将类的用法 "函数化"

函数后的 ”小括号“ 触发了__call__。

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)

>>> s = Student('Michael') >>> s() My name is Michael.

那么,怎么判断一个变量是对象还是函数呢?通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

 

  • 运算符重载

(a) 类的加法

关键字:__add__

#!/usr/bin/python3
 
class Vector:
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) v1 = Vector(2,10) v2 = Vector(5,-2) print (v1 + v2)

执行结果:

Vector(7,8)

    

(b) Iterable类

定义一个可遍历的类。或者具备generator特性的类,如下。

class Fib(object):
def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def __next__(self):

# (1) 计算下一个值 self.a, self.b
= self.b, self.a + self.b
# (2) 注意 "循环退出条件" if self.a > 100000: raise StopIteration()
return self.a # 返回下一个值 # Fib()返回的是自己本身;迭代的则是__next__提供的结果。 >>> for n in Fib():   ... print(n) ... 1 1 2 3 5 ... 46368 75025

 

(c) 数组模式

本质上是通过重新计算得出结果。

class Fib(object):
def __getitem__(self, idx): a, b = 1, 1 for x in range(idx): a, b = b, a + b return a >>> f = Fib() >>> f[0] 1 >>> f[1] 1 >>> f[2] 2 >>> f[3] 3 >>> f[10] 89 >>> f[100] 573147844013817084101

 

(d) 切片模式

class Fib(object):
def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 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() >>> f[0:5] [1, 1, 2, 3, 5] >>> f[:10] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。

最后,还有一个__delitem__()方法,用于删除某个元素。

 

 

 

 

Inheritance


多继承

  • 构造函数

方法一、可以通过直接 ”调用父类构造函数" 来实现。

parent.__init__(self, ...)

方法二、还有下面这个方式,至少不用再涉及到"父类”的名字,貌似好一些。

super(子类,self).__init__(参数1,参数2,....)

 

  • 多继承歧义

Ref: [c++] Class [C++参考]

(1) 菱形:近三层的路径二义性

钻石继承时,c++中采用虚继承解决;python 怎么办?使用 super(子类, self) 方式。

[C++ 策略]

采用虚继承,自然就没了路径二义性,因为不care这个 “虚路径” 也就没事了。

 

[Python 策略]

不直接调用parent相关的东西,而是采用 super,只提及自己的类名,这里避开了选择哪个 of “两个父类名字” 的问题

class Father(object):
    def __init__(self, name, *args, **kwargs):
        self.name = name
        print("我是父类__init__")

class Son_1(Father): def __init__(self, name, age, *args, **kwargs): print("我是Son_1的__init__") super(Son_1, self).__init__(name, *args, **kwargs) self.age = age class Son_2(Father): def __init__(self, name, gender, *args, **kwargs): print("我是Son_2的__init__") super(Son_2, self).__init__(name, *args, **kwargs)
self.gender = gender
class GrandSon(Son_1, Son_2): def __init__(self, name, age, gender):
print("我是GrandSon的__init__") super(GrandSon, self).
__init__(name, age, gender) def say_hello(self): print(self.name, self.age, self.gender)
grand_son
= GrandSon("老王", 24, "")

 

(2) 倒三角:近两层的调用歧义

若是两个父类中有相同的方法名,而在子类使用时未指定,python 从左至右 搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

也就是说:优先选择位置“靠左”所继承的父类的方案。

class Father(object):
    def __init__(self, name, *args, **kwargs):
        self.name = name
        print("我是父类__init__")

    def say_hello(self):
        print("i'm father.")


class Son_1(Father):
    def __init__(self, name, age, *args, **kwargs):
        print("我是Son_1的__init__")
        super(Son_1, self).__init__(name, *args, **kwargs)
        self.age = 100

    def say_hello(self):
        print("i'm son_1.")

    def fn_son_1(self):
        print("only son 1 can do it, age = {}".format(self.age) )


class Son_2():
    def __init__(self, name, gender, *args, **kwargs):
        print("我是Son_2的__init__")
        self.gender = gender
        # super(Son_2, self).__init__(name, *args, **kwargs)

    def say_hello(self):
        print("i'm son_2.")


class GrandSon(Son_2, Son_1):
    def __init__(self, name, age, gender):
        print("我是GrandSon的__init__")
# super(GrandSon, self).__init__(name, age, gender) Son_2.__init__(self, name, age, gender) Son_1.__init__(self, name, age, gender) grand_son = GrandSon("老王", 24, "") grand_son.fn_son_1()

Notice: GrandSon在这里是不宜使用super的,这种“自动化模式”会导致“只调用Son_2的__init__。

所以,这里还是使用了传统的 父类.__init__,这样,grand_son.fn_son_1()就可以正常执行了。

 

 

 

 

Polymorphism


家长代表权

  • "父类" 作为参数

[C++ 策略]

采用虚函数,纯虚函数(实现接口)。

 

[Python 策略]

该参数也可以兼容处理其子类。以下,对应了C++的虚函数。

class Father(object):
    def __init__(self, name, *args, **kwargs):
        self.name = name
        print("我是父类__init__")

    def say_hello(self):
        print("i'm father.")


class Son_1(Father):
    def __init__(self, name, age, *args, **kwargs):
        print("我是Son_1的__init__")
        super(Son_1, self).__init__(name, *args, **kwargs)
        self.age = 100

    def say_hello(self):
        print("i'm son_1.")

    def fn_son_1(self):
        print("only son 1 can do it, age = {}".format(self.age) )


class Son_2(Father):
    def __init__(self, name, gender, *args, **kwargs):
        print("我是Son_2的__init__")
        self.gender = gender
        super(Son_2, self).__init__(name, *args, **kwargs)

    def say_hello(self):
        print("i'm son_2.")


son_1 = Son_1("son 1", 24)
son_2 = Son_2("son 2", 'male')

设计一个函数,一并兼容处理son_1, son_2,怎么办?

>>> def say_something(father):
...     father.say_hello()

>>> say_something(son_1) i'm son_1. >>> say_something(son_2) i'm son_2.

 

  • "类父类" 作为参数

动态语言支持“鸭子类型”,具备“必要的”方法就能凑活的用了。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

class Timer(object):
    def run(self):
        print('Start...')

 

End.

posted @ 2018-02-04 14:53  郝壹贰叁  阅读(361)  评论(0编辑  收藏  举报