在其基类中创建当前类默认实例
Posted
技术标签:
【中文标题】在其基类中创建当前类默认实例【英文标题】:Create current class default instance in its base class 【发布时间】:2013-09-07 22:33:57 【问题描述】:序言: 我有一些对象,其中一些可以由默认构造函数创建并且无需修改,因此可以将这些对象视为“空”。有时我需要验证某个对象是否为“空”。可以通过以下方式完成(majik方法在基类Animal
中实现):
>>> a = Bird()
>>> b = Bird()
>>> a == b
True
>>> a == Bird()
True
所以问题:是否有可能(如果是,那么如何)实现这样的语法:
>>> a == Bird.default
True
至少这个(但前一个更甜):
>>> a == a.default
True
但是:在基类Animal
中实现default
(不要在所有派生类中重复它):
class Animal(object):
... tech stuff ...
- obj comparison
- obj representation
- etc
class Bird(Animal):
... all about birds ...
class Fish(Animal):
... all about fishes ...
当然我不需要解决方案来让Bird()
调用Animal
类:)
我想在基类中实现一种模板,它将删除派生类默认实例并将其唯一副本存储在派生类或实例属性中。我认为这可以通过使用元类来实现,但不知道如何。
类默认实例可以被认为是由其类的__init__()
实例化的任何对象(当然无需进一步的对象修改)。
更新
系统充满了对象,我只是想有可能将新创建的(默认情况下)创建的对象(例如无用的显示)与已经以某种方式修改过的对象分开。我这样做:
if a == Bird():
. . .
我不想创建新对象进行比较,直观地说,我希望有一个实例副本作为标准具,以便与此类的实例进行比较。对象类似于 JSON 并且只包含属性(除了隐式 __str__
、__call__
、__eq__
方法),所以我想保持这种使用内置 Python 功能的风格并避免使用显式定义的方法,如is_empty()
例如。这就像在交互式 shell 中输入一个对象,然后调用__str__
将其打印出来,这是隐含的,但很有趣。
【问题讨论】:
【参考方案1】:要实现第一个解决方案,您应该使用metaclass。
例如:
def add_default_meta(name, bases, attrs):
cls = type(name, bases, attrs)
cls.default = cls()
return cls
并将其用作(假设python3。在python2中,在类主体中设置__metaclass__
属性):
class Animal(object, metaclass=add_default_meta):
# stuff
class NameClass(Animal, metaclass=add_default_meta):
# stuff
请注意,您已经为Animal
的每个 子类重复了metaclass=...
。
如果您使用类及其__new__
方法而不是函数来实现元类,则可以继承它,即:
class AddDefaultMeta(type):
def __new__(cls, name, bases, attrs):
cls = super(AddDefaultMeta, cls).__new__(cls, name, bases, attrs)
cls.default = cls()
return cls
实现相同效果的另一种方法是使用类装饰器:
def add_default(cls):
cls.default = cls()
return cls
@add_default
class Bird(Animal):
# stuff
同样,您必须为 每个 子类使用装饰器。
如果你想实现第二种解决方案,即检查a == a.default
,那么你可以简单地重新实现Animal.__new__
:
class Animal(object):
def __new__(cls, *args, **kwargs):
if not (args or kwargs) and not hasattr(cls, 'default'):
cls.default = object.__new__(cls)
return cls.default
else:
return object.__new__(cls)
这将在创建类的第一个实例时创建空实例,并将其存储在default
属性中。
这意味着你可以同时做:
a == a.default
和
a == Bird.default
但是如果您没有创建任何Bird
实例,则访问Bird.default
会得到AttributeError
。
样式说明:Bird.Default
对我来说看起来很糟糕。 Default
是 Bird
的一个实例而不是类型,因此您应该根据 PEP 8 使用 lowercase_with_underscore
。
事实上,整件事对我来说看起来很可疑。您可以简单地使用is_empty()
方法。这很容易实现:
class Animal(object):
def __init__(self, *args, **kwargs):
# might require more complex condition
self._is_empty = not (bool(args) or bool(kwargs))
def is_empty(self):
return self._is_empty
然后,当子类创建一个不向基类传递任何参数的空实例时,_is_empty
属性将为True
,因此继承的方法将相应地返回True
,而在其他情况下一些参数将传递给基类,该基类会将_is_empty
设置为False
。
您可以尝试一下,以获得更健壮的条件,以便更好地与您的子类一起使用。
【讨论】:
感谢风格提示。你的灵魂:乍一看它有效,但它使所有实例都相同...... @rook 哪个解决方案?如果您的意思是将add_default_meta
作为函数,那么正如我在答案中所写,您必须在创建子类时指定metaclass=add_default_meta
,否则不会调用元类。使用“类版本”可以避免重复这一点,因为Animal.__class__
将是元类而不是type
。
我使用__new__
方法测试了第二种解决方案。我更喜欢那个,因为所有东西都打包在我长期受苦的基类中:) grc 想法很好,可以避免在子类中进行装饰,但它使所有实例都具有相同的类型。
@rook 你的意思是实现Animal.__new__
的那个?我刚刚在我的机器上进行了测试,它工作正常:Bird.default
是 Bird
实例,Dog.default
是 Dog
实例等。同时创建传递参数的实例也正确创建了对象。 (我做了一个小修复以使其在 python3 中正常工作,由于object.__new__
的不同语义而引发了TypeError
)
是的,这部分工作得很好,但现在所有实例都是相同的对象,即只引用一个对象。【参考方案2】:
另一个可能的元类:
class DefaultType(type):
def __new__(cls, name, bases, attrs):
new_cls = super(DefaultType, cls).__new__(cls, name, bases, attrs)
new_cls.default = new_cls()
return new_cls
您只需要为 Animal 类设置元类属性,因为所有派生类都会继承它:
class Animal(object):
__metaclass__ = DefaultType
# ...
class Bird(Animal):
# ...
这允许您同时使用两者:
a == Bird.default
和:
a == a.default
【讨论】:
以上是关于在其基类中创建当前类默认实例的主要内容,如果未能解决你的问题,请参考以下文章
如果对象没有默认构造函数并且我不允许在其类定义中创建一个对象,如何将对象放入节点中?
是否每个 Rails 应用程序都在其布局中创建了默认的 application.html.erb?