深入理解Python面向对象-类成员

来自:python爬虫实战之路,作者:星星o在线

Python是一个面向对象的语言,但是因为python语言的特性,我们大多的python程序员只是把它当做一个工具,在我们项目的实际使用中除了使用Django框架以外,其他的都是使用python最基础的方式,完全跟pythonic没有一点关系,当然主要还是因为我们能力太差。面向对象是书写各种框架的基础,python的面向对象不仅拥有通用面向对象的特性,而且还拥有一些极其强大的特性,可以让我们把它的便利性发挥到极致。本篇将详细介绍Python 类的成员、成员修饰符、类的特殊成员。

类的成员

类的成员可以分为三个方面:字段、方法和属性。

在定义一个类以后,方法、属性和静态字段都是属于类的,在内存中只保存一份,只有普通字段是属于类对象的,每个类对象都会创建并保存一份。

我们可以将存储区分为几种:程序存储区、全局存储区、静态存储区、栈存储区。

  • 程序存储区: 我们所有的普通函数、类函数、类等程序所存储的位置。

  • 全局存储区:所有全局变量的存储区。

  • 静态存储区:类的静态变量存储区。

  • 栈存储区:栈存储区就是局部变量存储,所有的局部变量、类对象等都存储在这里。

字段

字段包括:普通字段和静态字段,使用和定义都是不一样的,其最本质的区别就是内存中保存的位置不同。

  • 普通字段属于对象

  • 静态字段属于类

class Demo:
    aa = "我是静态字段"
    def __init__(self, name):
        self.bb = name

    def custom_func(self, name):
        self.cc = name
obj1 = Demo(11)
Demo.aa = 111111
obj1.aa = 1
obj1.custom_func(1111)
obj2 = Demo(22)
obj2.aa = 2
Demo.aa = 222222
obj2.custom_func(2222)

print("obj1.aa = ", obj1.aa)
print("obj1.bb = ", obj1.bb)
print("obj1.cc = ", obj1.cc)
print("Demo.aa = ", Demo.aa)

print("obj2.aa = ", obj2.aa)
print("obj2.bb = ", obj2.bb)
print("obj2.cc = ", obj2.cc)
print("Demo.aa = ", Demo.aa)

print("id(Demo.aa) = ", id(Demo.aa))
print("id(obj1.aa) = ", id(obj1.aa))
print("id(obj2.aa) = ", id(obj2.aa))

输出:

obj1.aa =  1
obj1.bb =  11
obj1.cc =  1111
Demo.aa =  222222
obj2.aa =  2
obj2.bb =  22
obj2.cc =  2222
Demo.aa =  222222
id(Demo.aa) =  4645988944
id(obj1.aa) =  4530158768
id(obj2.aa) =  4530158800

从上面我们可以看出,bb、cc为普通字段,每个类独有一份,相互不影响,而且需要使用类对象访问,而Demo.aa和obj.aa是不同的,我们看到obj.aa的行为与普通字段类似,如果去掉obj1.aa=xx和obj2.aa=xx,那么obj1.aa和Demo.aa是相同的,在内存中的地址也是相同的。这里涉及到python类的另外一种特性-临时属性。

临时属性:我们知道python和其他的非解释性语言不同,定义变量是不需要指定类型,我们可以这样理解,在赋值运算符=的左边就是我们定义的变量,简单粗暴一点理解,出现=就是定义变量。当我们使用obj1.aa=xx的时候,就是给obj1对象定义一个变量aa,为什么叫临时属性?因为这是对象专属的一个变量,只为当前对象使用,这个类的其他对象是没有的。可以看到,在我们没有执行obj1.aa=xx的时候,打印obj1.aa和Demo.aa的值是一样的,执行以后就不一样了,这个又牵扯到了作用域的问题,就先打住吧

通常情况下我们都使用普通字段,当一个变量在类的所有对象中共同使用,而且数据共享的时候,我们就可以使用静态字段。

方法

方法包括:普通方法、静态方法和类方法。他们的区别在于调用方式不同。

  • 普通方法:由对象调用;包含一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;

  • 类方法:由类调用; 包含一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;

  • 静态方法:由类调用;没有默认参数;

class Foo:
    FooName = "静态字段"
    def custom_func(self, name):
        # 普通方法,至少包含一个self参数
        print("普通方法")

    @classmethod
    def class_func(cls, name):
        # 类方法,至少包含一个cls参数
        print("类方法")

    @staticmethod
    def static_func(name):
        # 静态方法,没有默认参数
        print("静态方法")

f = Foo()

# 调用普通方法
f.custom_func("aaa")

# 调用类方法
Foo.class_func("bbb")

# 调用静态方法
Foo.static_func("ccc")

普通方法中可以直接使用对象的普通字段self.name
类方法中可以直接使用静态字段cls.FooName
静态方法中不能直接使用普通字段和静态字段

注意:静态字段Foo.FooName可以在任意地方调用,包括以上三种场景内,如果在普通方法中也可以使用 self.FooName调用,前提是对象没有同名的普通字段

属性  

上面我们已经介绍了Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。

对于属性,有以下两个知识点:

  • 基本使用

  • 两种定义方式

  1. 属性的基本使用

class Foo:
    def func(self):
        pass

    @property
    def prop(self):
        print("属性")

f = Foo()

# 调用函数
f.func()

# 调用属性
f.prop

通过上面的例子,我们知道属性有以下几点:

  • 普通方法上添加@property装饰器

  • 有且只能有一个self参数,不能额外增加参数

  • 调用时不需要括号

既然属性是普通方法的变种,那么它存在的意义是什么呢?有方法为什么还需要属性呢?
访问属性时可以制造出和访问字段完全相同的假象,它拥有字段的简洁性,又拥有方法的多功能性

我们在做网页数据展示的时候,页面上显示数据的数据,不可能一次性把数据库中的所有内容都显示在页面上,一般都是通过分页功能实现,所以每次请求都会根据当前页数current_page和每页显示数量page_count来取出指定范围的数据。

class Pager:
    def __init__(self, current_page):
        # 当前显示的页码
        self.current_page = current_page

        # 每页默认显示数据
        self.per_items = 10

    @property
    def start(self):
        return (self.current_page - 1) * self.per_items

    @property
    def end(self):
        return self.current_page * self.per_items

pages = Pager(1)
pages.start # 起始值
pages.end   # 结束值
  1. 属性的两种定义方式

属性的定义有两种方式:

  • 装饰器:在方法上应用装饰器

  • 静态字段:在类中定义值为property对象的静态字段

我们知道Python中的类有经典类和新式类之分,如果类继承自object,那么该类是新式类,新式类的属性比经典类更丰富。但是现在都已经使用python3了,而python3中默认类都继承自object,所以python3中全是新式类。

1. 装饰器方式:普通方法加上@property装饰器

class Goods:
    @property
    def price(self):
        print("@property")

    @price.setter
    def price(self, val):
        print("@price.setter: ", val)

    @price.deleter
    def price(self):
        print("@price.deleter")

obj = Goods()
obj.price            # 自动执行@property修饰的price方法,并获取方法的返回值
obj.price = 100  # 自动执行@price.setter修饰的price方法,并将100赋值给方法的参数
del obj.price      # 自动执行@price.deleter修饰的price方法
  属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法

分别对应同一个属性的:获取、修改、删除

2. 静态字段方式,使用property创建静态字段

property是一个类,在builtins.py文件中,初始化函数:def __init__(self, fget=None, fset=None, fdel=None, doc=None),有四个参数

  • 第一个参数fget是方法名,调用 对象.属性 时自动触发执行方法

  • 第二个参数fset是方法名,调用 对象.属性 = XXX 时自动触发执行方法

  • 第三个参数fdel是方法名,调用 del 对象.属性 时自动触发执行方法

  • 第四个参数doc是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息

class Foo:
    def __init__(self):
        self.price = 10

    def get_price(self):
        return self.price

    # set函数必须有两个参数
    def set_price(self, value):
        self.price = value

    def del_price(self):
        del self.price

    PRICE = property(get_price, set_price, del_price, "description Price")

f = Foo()
print(f.PRICE)    # 自动调用get_price
f.PRICE = 20     # 自动调用set_price
print(f.PRICE)    # 自动调用del_price
del f.PRICE        # 自动调用description Price
推荐↓↓↓
Python编程
上一篇:Python super().__init__和Base.__init__的区别 下一篇:Python操作Excel,你觉得哪个库更好呢?