面向对象Python - 高级特性



在本节中,我们将深入了解Python提供的一些高级特性。

我们类设计中的核心语法

在本节中,我们将了解Python如何允许我们在类中利用运算符。Python在很大程度上是对象和方法对对象的调用,即使它被一些方便的语法隐藏起来,这种情况仍然存在。

>>> var1 = 'Hello'
>>> var2 = ' World!'
>>> var1 + var2
'Hello World!'
>>>
>>> var1.__add__(var2)
'Hello World!'
>>> num1 = 45
>>> num2 = 60
>>> num1.__add__(num2)
105
>>> var3 = ['a', 'b']
>>> var4 = ['hello', ' John']
>>> var3.__add__(var4)
['a', 'b', 'hello', ' John']

所以,如果我们必须将魔法方法__add__添加到我们自己的类中,我们也可以这样做吗?让我们尝试一下。

我们有一个名为Sumlist的类,它有一个构造函数__init__,它将列表作为名为my_list的参数。

class SumList(object):
   def __init__(self, my_list):
      self.mylist = my_list
   def __add__(self, other):
     new_list = [ x + y for x, y in zip(self.mylist, other.mylist)]

     return SumList(new_list)
   
   def __repr__(self):
      return str(self.mylist)

aa = SumList([3,6, 9, 12, 15])

bb = SumList([100, 200, 300, 400, 500])
cc = aa + bb # aa.__add__(bb)
print(cc) # should gives us a list ([103, 206, 309, 412, 515])

输出

[103, 206, 309, 412, 515]

但是,有很多方法是由其他魔法方法内部管理的。以下是一些:

'abc' in var # var.__contains__('abc')
var == 'abc' # var.__eq__('abc')
var[1] # var.__getitem__(1)
var[1:3] # var.__getslice__(1, 3)
len(var) # var.__len__()
print(var) # var.__repr__()

继承内置类型

类也可以继承自内置类型,这意味着继承自任何内置类型并利用在那里找到的所有功能。

在下面的示例中,我们继承自字典,但随后我们实现了它的一个方法__setitem__。当我们在字典中设置键和值时,会调用此(setitem)。由于这是一个魔法方法,因此它将被隐式调用。

class MyDict(dict):

   def __setitem__(self, key, val):
      print('setting a key and value!')
      dict.__setitem__(self, key, val)

dd = MyDict()
dd['a'] = 10
dd['b'] = 20

for key in dd.keys():
   print('{0} = {1}'.format(key, dd[key]))

输出

setting a key and value!
setting a key and value!
a = 10
b = 20

让我们扩展之前的示例,在下面我们调用了两个名为__getitem__和__setitem__的魔法方法,当我们处理列表索引时,它们会被更好地调用。

# Mylist inherits from 'list' object but indexes from 1 instead for 0!
class Mylist(list): # inherits from list
   def __getitem__(self, index):
      if index == 0:
         raise IndexError
      if index > 0:
         index = index - 1
         return list.__getitem__(self, index) # this method is called when

# we access a value with subscript like x[1]
   def __setitem__(self, index, value):
      if index == 0:
         raise IndexError
      if index > 0:
      index = index - 1
      list.__setitem__(self, index, value)

x = Mylist(['a', 'b', 'c']) # __init__() inherited from builtin list

print(x) # __repr__() inherited from builtin list

x.append('HELLO'); # append() inherited from builtin list

print(x[1]) # 'a' (Mylist.__getitem__ cutomizes list superclass
               # method. index is 1, but reflects 0!

print (x[4]) # 'HELLO' (index is 4 but reflects 3!

输出

['a', 'b', 'c']
a
HELLO

在上面的示例中,我们在Mylist中设置了一个包含三个元素的列表,并且隐式地调用了__init__方法,当我们打印元素x时,我们得到了包含三个元素的列表(['a','b','c'])。然后我们向此列表追加另一个元素。之后我们请求索引1和索引4。但是,如果您查看输出,我们会从我们请求的(index-1)获取元素。正如我们所知,列表索引从0开始,但此处索引从1开始(这就是为什么我们得到列表的第一个元素的原因)。

命名约定

在本节中,我们将了解用于变量(尤其是私有变量)的名称以及全球Python程序员使用的约定。尽管变量被指定为私有,但Python中不存在隐私,这是设计使然。与任何其他有良好文档记录的语言一样,Python具有它所提倡的命名和样式约定,尽管它不强制执行这些约定。Python的创始人“Guido van Rossum”编写了一份样式指南,描述了最佳实践和名称的使用,称为PEP8。以下是此指南的链接:https://www.pythonlang.cn/dev/peps/pep-0008/

PEP代表Python增强提案,是一系列在Python社区中分发的文档,用于讨论建议的更改。例如,建议所有:

  • 模块名称 - all_lower_case
  • 类名和异常名 - CamelCase
  • 全局和局部名称 - all_lower_case
  • 函数和方法名 - all_lower_case
  • 常量 - ALL_UPPER_CASE

这些仅仅是建议,您可以根据需要进行修改。但是,由于大多数开发人员遵循这些建议,因此您的代码的可读性可能会降低。

为什么要遵循约定?

我们可以遵循PEP建议,因为它允许我们获得:

  • 对绝大多数开发人员来说更加熟悉
  • 对大多数代码阅读者来说更加清晰。
  • 将与在相同代码库上工作的其他贡献者的风格相匹配。
  • 专业软件开发人员的标志
  • 每个人都会接受你。

变量命名 - “公共”和“私有”

在Python中,当我们处理模块和类时,我们将某些变量或属性指定为私有。在Python中,不存在“私有”实例变量,除了在对象内部之外无法访问。私有仅仅意味着它们并非旨在被代码用户使用,而是旨在在内部使用。通常,大多数Python开发人员遵循一种约定,即以下划线为前缀的名称,例如_attrval(以下示例)应被视为API或任何Python代码(无论是函数、方法还是数据成员)的非公共部分。以下是我们遵循的命名约定:

  • 公共属性或变量(旨在被此模块的导入者或此类的用户使用) - regular_lower_case

  • 私有属性或变量(模块或类内部使用) - _single_leading_underscore

  • 不应被子类化的私有属性 - __double_leading_underscore

  • 魔法属性 - __double_underscores__(使用它们,不要创建它们)

class GetSet(object):

   instance_count = 0 # public
   
   __mangled_name = 'no privacy!' # special variable

   def __init__(self, value):
      self._attrval = value # _attrval is for internal use only
      GetSet.instance_count += 1

   @property
   def var(self):
      print('Getting the "var" attribute')
      return self._attrval

   @var.setter
   def var(self, value):
      print('setting the "var" attribute')
      self._attrval = value

   @var.deleter
   def var(self):
      print('deleting the "var" attribute')
      self._attrval = None

cc = GetSet(5)
cc.var = 10 # public name
print(cc._attrval)
print(cc._GetSet__mangled_name)

输出

setting the "var" attribute
10
no privacy!
广告