python:面向对象程序设计及property装饰器

Python面向对象设计特点

  • 具有面向对象的所有特征:封装、继承、多态;
  • 所有类的父类为object;
  • 子类可以对父类的任何方法都可能进行重写;
  • Python中没有提供重载和访问控制,但是属性可以用特殊名进行访问控制

定义类

类定义格式如下:

class A:
    pass
class B(A):
    pass
属性和方法

在java和C++中,分别提供了this引用和this指针,表示当前的调用对象或指针,在Python中,则提供了类似的self参数,在对方法进行调用时,会自动提供self参数,但是必须在参数列表中包含该参数,因此,方法的定义格式如下:

class A:
    def func(self):
        print("我是普通方法")

有了self参数,类的属性就是通过self参数来存取,如:

class A:
    def func(self,name,age):
        self.name = name
        self.age = age
        print("我是普通方法")    

完整的类定义如下:

class A:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def func(self):
        print(666)
    def __eq__(self, other):
        return self.name == other.name and self.age == other.age
    def __repr__(self):
        return "Animal('{0}',{1}".format(self.name,self.age)
    def __str__(self):
        return "Animal({0},{1}".format(self.name,self.age)

a1 = A('alex',1000)
print(a1.name) #'alex"
print(a1.func()) # 666  None
print(a1.__eq__(a1)) # True
print(a1.__str__()) #Animal(alex,1000
print(a1.__repr__()) #Animal('alex',1000

其中以_开头和结尾的方法都来自于基类object,当定义好类后,就可以了导入人类所在模块后使用了,

如:

import Module_A
dog = Module_A
dog.eat()

如果想要定义一个私有的属性,则在属性前面加上__就变成私有的了

如:

class A:
    def __init__(self,name='alex',age=1000):
        self.__name=name
        self.age=age
if __name__ == "__main__":
    dog = A()
    print(dog.__name)
    #AttributeError: 'A' object has no attribute '__name'
基本特殊方法

__new()____init()__方法: 
和其他语言不同,Python中要创建一个对象,当调用dog = Module_Animal.Animal("Dog")时,执行了两个步骤:

  • 第一步,使用__new__()方法创建该对象;
  • 第二步,使用__init__()方法对其进行初始化;

在创建类时,应该重写__init__()方法进行初始化,__new__()不必须进行重写。在创建对象时如果发现没有提供,则自动调用object.__new__()。 
如果重写了__init__()方法并且想要调用基类该方法,则可以通过super()方法:

def __init__(self):
    super().__init__()

__eq__(self, other)__ne__(self, other)方法: 
这两个方法用于两个对象之间进行比较,对于__eq__(),如果self和other相等,则返回True;对于__ne__(),如果self和other不等,则返回True,如:

dog = Animal("Dog")
dog.eat()
cat = Animal("cat")
cat.eat()
print(dog == cat)   # return False
isEqual = dog.__eq__(cat)
isNotEqual = dog.__ne__(cat)
print(isEqual) # return False
print(isNotEqual)  # return True
  • 默认情况下,所有的自定义类都支持==进行比较,但总是返回False(除非dog == dog),因此可以通过__eq__()来实现比较操作。
  • 如果提供了__eq__()方法但没有提供__ne__()方法,Python会自动提供__ne__()!=操作符;如果没有提供__eq__(),调用结果为NotImplemented。
  • 默认情况下,自定义类的所有实例都是可哈希运算的,可以调用__hash__()方法,也可以作为字典的键,但是如果重写了__eq__()方法,则该类的实例就不是可哈希运算的了。
  • 应避免非同类型间进行比较,可用内置函数isInstance(obj,Obj)处理,第一个参数为对象,第二个为类型,如:
def __eq__(self, other):
    if not isinstance(other,Animal):
        return NotImplementedError
    return self.name == other.name and self.age == other.age
  • 对于比较操作符,Python中都提供了特殊的方法: 
    • __le__(self,other):self <= other,返回Ture
    • __lt__(self,other):self < other,返回Ture
    • __ge__(self,other):self >= other,返回Ture
    • __gt__(self,other):self > other,返回Ture
    • __ne__(self,other):self != other,返回Ture
    • __eq__(self,other):self == other,返回Ture

__repr__()__str__()方法: 
调用内置repr()函数时,会调用给定对象的__repr__()方法,该方法用于返回一个特殊的字符串,该字符串可通过内置eval()函数生成一个Python对象,如:

def __repr__(self):
    return "Animal('{0}',{1})".format(self.name,self.age)
if __name__ == "__main__":
    cat = Animal("Cat")
    ani = eval(repr(cat))  # return Animal('Cat',0)
    print(ani)  # Animal(Cat,0)

调用内置函数str()时,会调用给定对象的__str__()方法,该方法也会返回一个字符串,和__repr__()的区别主要是该方法产生的字符串主要是便于理解,而不是为了传递给eval()函数。 
如:

def __str__(self):
    return "Animal({0},{1})".format(self.name, self.age)
    print(str(ani))  # Animal(Cat,0)
  • 如果实现了__repr__()而没有实现__str__()时,则在调用str()、print(obj)时也会执行__repr__()方法。 
    __hash__()方法: 
    默认情况下,所有的类都是可以哈希运算的,但是如果类实现了__eq__()方法,那么则不可进行哈希运算,通过提供该方法可以使类能狗进行哈希运算,实现如下:
def __hash__(self):
    return hash(id(self))

内置hash()方法根据独一无二的ID来计算哈希值,内置id()方法返回一个独一无二的整数

继承和多态

Python中定义一个类继承另一个类格式如下:

class Subclass(A):
    pass

子类可以对父类的方法进行重写,如果子类想调用父类的方法,可以使用super()进行调用,如:

class Animal:
    def __init__(self,name="Animal",age=0):
        self.__name = name
        self.age = age

    def eat(self):
        print('Animal is eating')

    def shout(self):
        print("Animal is shouting")


class Dog(Animal):
    def __init__(self,name="Dog",age=0):
        # 调用Animal的__init__()方法
        super().__init__()
        self.name = name
        self.age = age

    def shout(self):
        print("Dog is Shouting")

    def eat(self):
        print("Dog is eating")

class Cat(Animal):
    def __init__(self,name="Cat"):
        self.name = name

    def shout(self):
        print("Cat is shouting")

    def eat(self):
        print("Cat is eating")

在java等面向对象语言中,多态是指子类的引用指向父类的实例,但是在Python中,可能不能用这种方式来理解,因为Python中定义一个变量或对象并不需要声明数据类型,因此对于Python而言,其多态的表现形式就是:如果子类对父类方法进行了覆盖,则子类对象调用该方法时,会调用子类对方法的实现。如:

if __name__ == "__main__":

    ani = Animal()
    dog = Dog()
    cat = Cat()
    ani.eat()  # Animal is eating
    dog.eat()  # Dog is eating
    cat.eat()  # Cat is eating

python提供了isinstance(o,Obj)方法,可以判断对象o是否是Obj类型,因此,在使用多态时,可以这样使用:

# 定义一个函数
def animal_eat(animal):
    animal.eat()

if isinstance(dog,Animal):
    animal_eat(dog) # Dog is eating
if isinstance(cat,Animal):
    animal_eat(cat) # Cat is eating
 

property装饰器

在面向对象的设计中,常常要将属性进行封装,提供setter/getter方法对属性进行操作,Python中也可以提供setter/getter进行对属性的封装,从而保证数据的安全性,但是并不推荐使用,因为有更优的方式可以属性的安全性,下面逐步进行分析。 
现在定义一个类:

import math

class Circle:

    def __init__(self, radius, x=0, y=0):
        self.radius = radius

    def area(self):
        return math.pi * (self.radius ** 2)


if __name__ == "__main__":

    c1 = Circle(2)
    c1.radius = -3
    print(c1.area())  
    print(c1.radius)  # -3

可以看到,属性radius有可能被设置为不合理的值,因此,可以将radius设置为私有属性,同时提供setter/getter方法对其进行设置,修改该类实现如下:

class Circle:

    def __init__(self, radius, x=0, y=0):
        if radius <= 0: 
            self.__radius = 1
        self.__radius = radius

    def set_radius(self, radius):
        assert radius >= 0, "radius can't be zero"
        self.__radius = radius

    def get_radius(self):
        return self.__radius


if __name__ == "__main__":

    c1 = Circle(2)
    c1.set_radius(-3)  # AssertionError: radius can't be zero
    print(c1.area())
    print(c1.get_radius())  # 2

通过setter/getter可以将属性封装起来,可以保证属性的安全性,但是这种方式比较繁琐,在Python中,更倾向于使用Property装饰器,使用方式如下:

# class A:
#     def __init__(self,name,age):
#         self.name = name
#         self.age = age
#     def func(self):
#         print(666)
#     def __eq__(self, other):
#         return self.name == other.name and self.age == other.age
#     def __repr__(self):
#         return "Animal('{0}',{1}".format(self.name,self.age)
#     def __str__(self):
#         return "Animal({0},{1}".format(self.name,self.age)
#
# a1 = A('alex',1000)
# print(a1.name) #'alex"
# print(a1.func()) # 666  None
# print(a1.__eq__(a1)) # True
# print(a1.__str__()) #Animal(alex,1000
# print(a1.__repr__()) #Animal('alex',1000


class A:
    def __init__(self,name='alex',age=1000):
        self.__name=name
        self.age=age
if __name__ == "__main__":
    dog = A()
    print(dog.__name)
    #AttributeError: 'A' object has no attribute '__name'
 

Property装饰器是一个函数,该函数以一个方法作为参数,并返回修饰后的版本。但是通常并不使用该方法,而是通过@符号来标记,如上例所示。property()是一个内置函数,至多可以接受四个参数:get参数、set参数、delete参数、docstring参数。@property相当于property(get)。 
在上例中,创建了一个__radius私有属性,然后通过property装饰器进行其getter、setter的设置。需要注意:

  • getter和setter有同样的名称,如def radius(self);
  • 当使用@property创建特性后,每个创建的特性都包含getter、setter、deleter、docstring等属性,并且都是可用的。
  • getter为@property设置的方法,其他属性由python设置。

当创建radius装饰器后,就可以通过radius.setter和radius.getter获取和设置__radius属性了,同时保证了私有属性的安全性,如:

c1.radius = 0  # AssertionError: radius can't be zero
c1.radius = 3  # 调用radius(self,radius),相当于radius.setter
print(c1.radius)  # 2  调用radius.setter

猜你喜欢

转载自www.cnblogs.com/while-number/p/9272763.html