返回顶部

请叫我杨先生

导航

Python 面向对象复习

面向对象

面向过程编程

  1. 导入各种外部库
  2. 设计各种全局变量
  3. 写一个函数完成某个功能
  4. 写一个函数完成某个功能
  5. 写一个函数完成某个功能
  6. 写一个函数完成某个功能
  7. 写一个函数完成某个功能
  8. ......
  9. 写一个main函数作为程序入口

面向对象编程和函数式编程(面向过程编程)都是程序设计的方法,不过稍有区别。

面向对象编程

  1. 导入各种外部库
  2. 设计各种全局变量
  3. 决定你要的类
  4. 给每个类提供完整的一组操作
  5. 明确地使用继承来表现不同类之间的共同点
  6. 根据需要,决定是否写一个main函数作为程序入口

Python类的一些基础:

  1. 类(Class): 用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。其中的对象被称作类的实例。
  2. 实例:也称对象。通过类定义的初始化方法,赋予具体的值,成为一个”有血有肉的实体”。
  3. 实例化:创建类的实例的过程或操作。
  4. 实例变量:定义在实例中的变量,只作用于当前实例。
  5. 类变量:类变量是所有实例公有的变量。类变量定义在类中,但在方法体之外。
  6. 数据成员:类变量、实例变量、方法、类方法、静态方法和属性等的统称。
  7. 方法:类中定义的函数。
  8. 静态方法:不需要实例化就可以由类执行的方法
  9. 类方法:类方法是将类本身作为对象进行操作的方法。
  10. 方法重写:如果从父类继承的方法不能满足子类的需求,可以对父类的方法进行改写,这个过程也称override。
  11. 封装:将内部实现包裹起来,对外透明,提供api接口进行调用的机制
  12. 继承:即一个派生类(derived class)继承父类(base class)的变量和方法。
  13. 多态:根据对象类型的不同以不同的方式进行处理。

调用类的三种方式

(1)实例方法

class dd: 
    def __init__(self ,name): 
        self.name = name 
    def show(self): 
        print("My name is {}".format(self.name))

dd = dd("Yang") 
dd.show()
My name is Yang

(2)静态方法

静态方法由类调用,没有默认参数。 将实例化中的self删除,然后在函数上方加上@staticmethod就变成静态方法了。它属于类,和实例无关。建议只使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)

摘要: 经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法. 比如更改环境变量或者修改其他类的属性等能用到静态方法. 这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码,造成维护困难。

class ff: 
    @staticmethod
    def mod():
        print("@staticmethod 关键字的使用") 
        
ff = ff()
ff.mod(),ff.mod()
@staticmethod 关键字的使用
@staticmethod 关键字的使用

(3)类方法

类方法由类调用,采用@calssmethod装饰,至少传入一个cls参数(代指类本身,可以看成是self)。 执行类方法时,自动将调用该方法的类赋值给cls。建议只使用类名.类方法的调用方式。(虽然也可以使用实例名.类方法的方式调用)

class gg: 
    def __init__(self , name , age): 
        self.name = name 
        self.age = age  
    def outer(self): 
        print("Name :",self.name,"   Age:",self.age) 
        
    @classmethod
    def inner(cls,name,age):
        cls.name = name 
        cls.age = age  
        if cls.age >= 20 : 
            print(cls.name,"都这么大了还想  打飞机")
        else : 
            print(cls.name, "你还很年轻不要 打飞机")
        
a = gg('name',20) 
a.outer() ,gg.inner("小伙子",21) ,a.inner("小伙子",21)
Name : name    Age: 20
小伙子 都这么大了还想  打飞机
小伙子 都这么大了还想  打飞机

类的特性 (1)封装

# 从Pytorch官网上截取下来的torchvision.transforms.ToTensor类的内容 ,如下就是封装了ToTensor
class ToTensor:
    def __call__(self, pic):
        return F.to_tensor(pic)

    def __repr__(self):
        return self.__class__.__name__ + '()'

类的特性 -(2)继承

当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:

class Animal(object):
    def run(self):
        print('Animal is running...')  
        
class Dog(Animal):
    pass
class Cat(Animal):
    pass

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

类的特性 - (3)多态

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

#要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
class Animal(object):
    def run(self):
        print('Animal is running...')  
        
class Dog(Animal):
    def run(self):
        print('Dog is running...')  
        
class Cat(Animal):
    def run(self):
        print('Cat is running...')  

def run_twice(animal):
    animal.run()
    animal.run()
    
run_twice(Cat()) ,run_twice(Dog()),run_twice(Animal())
Cat is running...
Cat is running...
Dog is running...
Dog is running...
Animal is running...
Animal is running...

多态的优势一,我们只要知道他是Animal的这个类型,便可以随便调用run()方法,不用注重过多的细节 , 调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

Magic method

在上面有提到除了init之外还有iter,reverse的方法,这里就详细说下除了init初始化还有哪些别的方法。

  • init : 构造函数,在生成对象时调用
  • del : 析构函数,释放对象时使用
  • repr : 打印,转换
  • setitem : 按照索引赋值
  • getitem: 按照索引获取值
  • len: 获得长度
  • cmp: 比较运算
  • call: 调用
  • add: 加运算
  • sub: 减运算
  • mul: 乘运算
  • div: 除运算
  • mod: 求余运算
  • pow: 幂

__doc__

__doc__ 说明性文档和信息。Python自建,无需自定义。

 class Foo:
    def func(self): 
        self.name = "I am  a hamsome Boy"
        print('I am 打印信息')
        pass 

class Foo2(Foo): 
    pass 
Foo.__doc__,Foo2.func.__doc__
(None, None)

__init__()

__init__() 实例化方法,通过类创建实例时,自动触发执行。

class Foo:
    def __init__(self, name):
        self.name = name
        self.age = 18
obj = Foo('jack') # 自动执行类中的 __init__ 方法

__module__ 和 __class__

__module__ 表示当前操作的对象在属于哪个模块。
__class__ 表示当前操作的对象属于哪个类。
这两者也是Python内建,无需自定义。

class Foo:
    pass
obj = Foo()
obj.__module__   ,  obj.__class__
('__main__', __main__.Foo)

__del__()

析构方法,当对象在内存中被释放时,自动触发此方法。

注:此方法一般无须自定义,因为Python自带内存分配和释放机制,除非你需要在释放的时候指定做一些动作。析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

class Foo: 
    def __del__(self):
        print("我被回收了")
        
obj = Foo()
我被回收了

__call__()

如果为一个类编写了该方法,那么在该类的实例后面加括号,可会调用这个方法。

注:构造方法的执行是由类加括号执行的,即:对象 = 类名(),而对于__call__() 方法,是由对象后加括号触发的,即:对象() 或者 类()()

class ToTensor:
    def __call__(self):
        return "F.to_tensor(pic)"

    def __repr__(self):
        return self.__class__.__name__ + '()'
    
tensor = ToTensor() # 执行__init__ 
tensor() # 执行__call__ 		
'F.to_tensor(pic)'

__dict__

列出类或对象中的所有成员!非常重要和有用的一个属性,Python自建,无需用户自己定义。

class Province:
    country = "China" 
    def __init__(self,name, count):
        self.name = name 
        self.count = count 
    
    def func(self,*args,**kwargs): 
        print('func') 
        
# 获取类的成员
Province.__dict__
# 获取 对象obj1 的成员 
obj1 = Province('HeBei',10000)
obj1.__dict__ # {'name': 'HeBei', 'count': 10000} 
# 获取 对象obj2 的成员 
obj2 = Province('HeNan', 3888)
obj2.__dict__# {'name': 'HeNan', 'count': 3888}

__str__()

如果一个类中定义了str()方法,那么在打印对象时,默认输出该方法的返回值。这也是一个非常重要的方法,需要用户自己定义。 

# 定义了__str__()方法后,打印结果是:'jack'。
class Foo:
    def __str__(self):
        return 'jack'

obj = Foo()
print(obj)
我被回收了
jack

__getitem__ () , __setitem__() ,__delitem__()

取值、赋值、删除这“三剑客”的套路,在Python中,我们已经见过很多次了,比如前面的@property装饰器。
Python中,标识符后面加圆括号,通常代表执行或调用方法的意思。而在标识符后面加中括号[],通常代表取值的意思。Python设计了getitem()、setitem()、delitem()这三个特殊成员,用于执行与中括号有关的动作。它们分别表示取值、赋值、删除数据。

a = 标识符[] :   执行__getitem__方法
标识符[] = a  :   执行__setitem__方法
del 标识符[] :   执行__delitem__方法
class Foo: 
    def __getitem__(self,key): 
        print('__getitem__',key)
        
    def __setitem__(self,key,value): 
        print("__setitem__",key,value) 
    
    def __delitem__(self,key): 
        print("__delitem__",key) 

f = Foo() 
result = f["K1"] # __getitem__ K1 自动执行 __getitem__  
f["K2"] = "K2"  # __setitem__ K2 qqqq 自动执行 __setitem__
del f["K2"] # __delitem__ K2 自动执行 __delitem__  

__iter__()

这是迭代器方法!列表、字典、元组之所以可以进行for循环,是因为其内部定义了 iter()这个方法。如果用户想让自定义的类的对象可以被迭代,那么就需要在类中定义这个方法,并且让该方法的返回值是一个可迭代的对象。当在代码中利用for循环遍历对象时,就会调用类的这个iter()方法。

class Foo: 
    def __init__(self,sq): 
        self.sq = sq 
    
    def __iter__(self): 
        return iter(self.sq)

obj = Foo([1,2,3,4,5]) 
for i in obj: 
    print(i,end=",") # 1,2,3,4,5,
class Foo:
    def __init__(self):
        pass
    def __iter__(self):
        yield 1
        yield 2
        yield 3
obj = Foo()
for i in obj:
    print(i,end=",")

__len__()

在Python中,如果你调用内置的len()函数试图获取一个对象的长度,在后台,其实是去调用该对象的len()方法,所以,下面的代码是等价的:

Python的list、dict、str等内置数据类型都实现了该方法,但是你自定义的类要实现len方法需要好好设计。

len("ABC") == "ABC".__len__() # True 

__repr__()

这个方法的作用和str()很像,两者的区别是str()返回用户看到的字符串,而repr()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。通常两者代码一样。

class Foo:
    def __init__(self,name):
        self.name = name
    def __str__(self): 
        return "this is %s" %self.name 

f = Foo("Xu Guangtao")
print(f) # this is Xu Guangtao 
  • __add__: 加运算
  • __sub__: 减运算
  • __mul__: 乘运算
  • __div__: 除运算
  • __mod__: 求余运算
  • __pow__: 幂运算

这些都是算术运算方法,需要你自己为类设计具体运算代码。有些Python内置数据类型,比如int就带有这些方法。Python支持运算符的重载,也就是重写。

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(1,2) 
v2 = Vector(2,3) 
str(v1+v2) # 自动调用 __add__()方法 

__author__作者信息

__author__ = "Jack" 

def show(): 
    print(__author__)

show() # Jack

__slots__

Python作为一种动态语言,可以在类定义完成和实例化后,给类或者对象继续添加随意个数或者任意类型的变量或方法,这是动态语言的特性。
如果我想限制实例可以添加的变量,比如我只能控制其添加 name 或者 age 其他的像score 这种属性添加不了

需要提醒的是,slots定义的属性仅对当前类的实例起作用,对继承了它的子类是不起作用的。想想也是这个道理,如果你继承一个父类,却莫名其妙发现有些变量无法定义,那不是大问题么?如果非要子类也被限制,除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。

def A():
    print("AAAA")
    
class Joo:
    __slots__ = ("name","age") 
    pass  

q = Joo() 
obj.name = "Jack" # 动态添加变量 
# obj,age = 10.1 
#obj.score = 100  # 'Foo' object has no attribute 'score' 
# 但是无法限制给类添加方法
Joo.show = print_doc
obj.show() # AAAA

私有成员

class obj:
    def __init__(self,name):
        self.name=name
    def pri(self):
        print(self.name)
    __age = 18
    # 加上双下划线的就是私有变量,只能在类的内部访问,外部无法访问
a = obj('zhao')
a.pri()
# 如果要在类中调用这个私有成员,可以这么用 
class obj: 
    def __init__(self,name):
        self.name = name  
        
    def prin(self): 
        print(self.name)
        
    __age = 19  
    
    @classmethod 
    def pri(cls): 
        print(cls.__age)  
        
obj.pri() # 直接通过 class_name.method() 的方式来调用 Class Method 
a = obj("Yang") 
a.prin()

参考文章/文献:
史上最全 Python 面向对象编程

posted on 2021-12-28 16:44  YangShusen'  阅读(72)  评论(0编辑  收藏  举报