Python 类 语法总结

Python 类 语法总结

两句废话

最近复习 Python,把自己看到的一些 Python 语法中需要注意的点总结在这里。

名称和对象

由于 Python 的赋值操作为引用赋值,所以多个变量名可以绑定到同一个对象上。这种特性的好处表现为,因为别名在某些方面表现得像指针,所以传递一个对象的代价很低,而且函数修改了作为参数传递的对象,调用者也看得到更改。

Python 作用于和命名空间

这个地方的知识点没怎么看懂,先写在这里,后面有机会再填坑

namespace 是一个从名字到对象的映射。和其他编程语言的作用一样,命名空间可以隔离不同命名空间的变量。

  • 一个函数的本地命名空间在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。每次递归调用都会又它的本地命名空间。

  • 一个 作用域 是一个命名空间可直接访问的 Python 程序的文本区域。 这里的 “可直接访问” 意味着对名称的非限定引用会尝试在命名空间中查找名称。

  • 如果一个名称被声明为全局变量,则所有引用和赋值将直接指向包含该模块的全局名称的中间作用域。 要重新绑定在最内层作用域以外找到的变量,可以使用 nonlocal 语句声明为非本地变量。 如果没有被 nonlocal 声明为非本地变量,这些变量将是只读的(尝试写入这样的变量只会在最内层作用域中创建一个 新的 局部变量,而同名的外部变量保持不变)。

  • global 语句可被用来表明特定变量生存于 全局作用域 并且应当在其中被重新绑定; nonlocal 语句表明特定变量生存于 外层作用域 中并且应当在其中被重新绑定。

    例子:

    def scope_test():
        def do_local():
            spam = "local spam"
    
        def do_nonlocal():
            nonlocal spam
            spam = "nonlocal spam"
    
        def do_global():
            global spam
            spam = "global spam"
    
        spam = "test spam"
        do_local()
        print("After local assignment:", spam)
        do_nonlocal()
        print("After nonlocal assignment:", spam)
        do_global()
        print("After global assignment:", spam)
    
    scope_test()
    print("In global scope:", spam)
    
    # 代码输出
    """
    After local assignment: test spam
    After nonlocal assignment: nonlocal spam
    After global assignment: nonlocal spam
    In global scope: global spam
    """
    

    局部 赋值(这是默认状态)不会改变 scope_test 对 spam 的绑定。 nonlocal 赋值会改变 scope_test 对 spam 的绑定,而 global 赋值会改变模块层级的绑定。

定义类

通过关键字 class 定义一个类,class 之后为类的名称并以冒号结尾

class ClassName:
   """类的帮助信息"""   #类文档字符串
   class_suite  #类体

创建实例对象

类实例化类似函数调用方式(类的 实例化 是使用函数表示法)

# "创建 Employee 类的第一个对象"
emp1 = Employee("Zara", 2000)
# "创建 Employee 类的第二个对象"
emp2 = Employee("Manni", 5000)

实例对象的属性引用

实例对象的属性引用通过 ObjectName.MethodName 实现,即通过 . 运算符实现

访问限制

Python 中对象可以通过访问符直接访问类的属性和方法。如果想内部属性不被外部访问,可以把属性的名称前加上两个下划线 __,使其变为私有属性,只有类内部能够访问。单下划线开头的实例变量虽然外部可以被访问,但是约定这样的变量应该被是为私有变量。

使用双下划线开始会导致访问名称变成其他形式。这种修改的作用是继承——这种属性通过继承是无法被覆盖的。如:

class B:
    def __init__(self):
        self.__private = 0

    def __private_method(self):
        pass

    def public_method(self):
        pass
        self.__private_method()

在 B 类中,私有属性会被分别重命名为 _B__private_B__private_method

有时候要定义的一个 变量和某个保留关键字 冲突,这时候可以使用 单下划线 作为后缀,例如:

lambda_ = 2.0 # Trailing _ to avoid clash with lambda keyword

需要区别的是,Python 中以双下划线开头和结尾的对象,是特殊对象 __XXX__

继承

类的继承语法如下:

class 派生类名(基类名)
    ...

多重继承的语法如下:

class SubClassName (ParentClass1[, ParentClass2, ...]):
    ...

派生类同样支持 Java 和 C++ 中的方法的重写、方法重载以及运算符重载。

Python 对象销毁

Python 的垃圾回收采用 Java 垃圾回收相同的技术实现,即采用引用计数实现变量垃圾回收。

修改实例的字符串表示

要改变一个实例的字符串表示,可重新定义它的 __str__()__repr__() 方法。例如:

class Pair:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Pair({0.x!r}, {0.y!r})'.format(self)

    def __str__(self):
        return '({0.x!s}, {0.y!s})'.format(self)

内置的 repr() 函数返回这个字符串,跟我们使用交互式解释器显示的值是一样的。 str() 方法将实例转换为一个字符串,使用 str() 或 print() 函数会输出这个字符串。

>>> p = Pair(3, 4)
>>> p
Pair(3, 4) # __repr__() output
>>> print(p)
(3, 4) # __str__() output
>>>

自定义 repr() 和 str() 通常是很好的习惯,因为它能简化调试和实例输出。

让对象支持上下文管理协议

为了让一个对象兼容 with 语句,类的定义中需要实现 __enter__()__exit__() 方法。 例如,考虑如下的一个类,它能为我们创建一个网络连接:

from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):
        self.sock.close()
        self.sock = None

from functools import partial

conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
    # conn.__enter__() executes: connection open
    s.send(b'GET /index.html HTTP/1.0\r\n')
    s.send(b'Host: www.python.org\r\n')
    s.send(b'\r\n')
    resp = b''.join(iter(partial(s.recv, 8192), b''))
    # conn.__exit__() executes: connection closed

当出现 with 语句的时候,对象的 __enter__() 方法被触发, 它返回的值(如果有的话)会被赋值给 as 声明的变量。然后,with 语句块里面的代码开始执行。 当 with 代码块中的代码执行结束后,__exit__() 方法被触发进行清理工作。不管 with 中的代码块发生什么,该过程一定会被执行完,即使发生了异常。

使代码在 with 语句中执行,可以完成加锁和解锁的一些繁琐而必要的工作。

创建大量对象时节省内存方法

当程序要创建大量(可能上百万)的对象,会导致占用很大的内存。对于主要是用来当成 简单的数据结构 的类而言,你可以通过给类添加 __slots__ 属性来极大的减少实例所占的内存。

class Date:
    __slots__ = ['year', 'month', 'day']
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

当你定义 __slots__ 后,Python就会为实例使用一种更加紧凑的内部表示。 但是会产生以下几点影响:

  1. 该不能再给实例添加新的属性了,只能使用在 __slots__ 中定义的那些属性名
  2. 定义了slots后的类不再支持一些普通类特性了,比如多继承。Python的很多特性都依赖于普通的基于字典的实现

所以,尽管slots看上去是一个很有用的特性,很多时候还是得减少对它的使用冲动

创建可管理的属性

如果想给某个实例的属性增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。可以将它定义为一个 property,即用 @property 装饰器修饰。给一个属性增加简单的类型检查的例子:

class Person:
    def __init__(self, first_name):
        self.first_name = first_name

    # Getter function
    @property
    def first_name(self):
        return self._first_name

    # Setter function
    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    # Deleter function (optional)
    @first_name.deleter
    def first_name(self):
        raise AttributeError("Can't delete attribute")

"""
>>> a = Person('Guido')
>>> a.first_name # Calls the getter
'Guido'
>>> a.first_name = 42 # Calls the setter
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "prop.py", line 14, in first_name
        raise TypeError('Expected a string')
TypeError: Expected a string
>>> del a.first_name
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
AttributeError: can`t delete attribute
>>>
"""

Properties还是一种定义动态计算attribute的方法。 这种类型的attributes并不会被实际的存储,而是在需要的时候计算出来。

import math
class Circle:
    def __init__(self, radius):
        self.radius = radius

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

    @property
    def diameter(self):
        return self.radius * 2

    @property
    def perimeter(self):
        return 2 * math.pi * self.radius

"""
>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area  # Notice lack of ()
50.26548245743669
>>> c.perimeter  # Notice lack of ()
25.132741228718345
>>>
"""

不要写有大量重复代码的property定义, 重复代码会导致臃肿、易出错和丑陋的程序。通过使用装饰器或闭包,有很多种更好的方法来完成同样的事情。

参考链接

docs.python

菜鸟教程——Python

个人博客网站——Python中的面向对象编程

python3-cookbook —— 类与对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值