internal circular class references

E

Ethan Furman

Greetings List!

I'm writing a wrapper to the datetime.date module to support having no
date. Its intended use is to hold a date value from a dbf file, which
can be empty.

The class is functional at this point, but there is one thing I would
like to change -- datetime.date.max and datetime.date.min are class
attributes of datetime.date, and hold datetime.date values. At this
point I have to have two lines outside the actual class definition to do
the same thing, e.g.:

<trimmed down class code>
class NullDate(object):
"adds null capable DateTime.Date constructs"
__slots__ = ['_date']
def __new__(cls, date='', month=0, day=0):
nulldate = object.__new__(cls)
nulldate._date = ""
.
.
.
return nulldate
def __getattr__(self, name):
if self:
attribute = self._date.__getattribute__(name)
return attribute
else:
if callable(dt.date.__dict__[name]):
return int
else:
return 0
def __nonzero__(self):
if self._date:
return True
return False
@classmethod
def fromordinal(cls, number):
if number:
return cls(dt.date.fromordinal(number))
else:
return cls()
NullDate.max = NullDate(dt.date.max)
NullDate.min = NullDate(dt.date.min)
</trimmed down class code>

How can I move those last two lines into the class definition so that:
1) they are class attributes (not instance), and
2) they are NullDate type objects?

~ethan~
 
J

James Stroud

Ethan said:
Greetings List!

I'm writing a wrapper to the datetime.date module to support having no
date. Its intended use is to hold a date value from a dbf file, which
can be empty.

The class is functional at this point, but there is one thing I would
like to change -- datetime.date.max and datetime.date.min are class
attributes of datetime.date, and hold datetime.date values. At this
point I have to have two lines outside the actual class definition to do
the same thing, e.g.:

<trimmed down class code>
class NullDate(object):
"adds null capable DateTime.Date constructs"
__slots__ = ['_date']
def __new__(cls, date='', month=0, day=0):
nulldate = object.__new__(cls)
nulldate._date = ""
.
.
.
return nulldate
def __getattr__(self, name):
if self:
attribute = self._date.__getattribute__(name)
return attribute
else:
if callable(dt.date.__dict__[name]):
return int
else:
return 0
def __nonzero__(self):
if self._date:
return True
return False
@classmethod
def fromordinal(cls, number):
if number:
return cls(dt.date.fromordinal(number))
else:
return cls()
NullDate.max = NullDate(dt.date.max)
NullDate.min = NullDate(dt.date.min)
</trimmed down class code>

How can I move those last two lines into the class definition so that:
1) they are class attributes (not instance), and
2) they are NullDate type objects?

~ethan~

I resisted posting a similar question recently. After much
consideration, I realized that the inability to reference a class inside
its own definition must have been a deliberate design of the language.
So the short answer is you can't.

The way you have done it is best--its not a hack and is good style.

James
 
C

Carl Banks

Greetings List!

I'm writing a wrapper to the datetime.date module to support having no
date.  Its intended use is to hold a date value from a dbf file, which
can be empty.

The class is functional at this point, but there is one thing I would
like to change -- datetime.date.max and datetime.date.min are class
attributes of datetime.date, and hold datetime.date values.  At this
point I have to have two lines outside the actual class definition to do
the same thing, e.g.:

<trimmed down class code>
   class NullDate(object):
       "adds null capable DateTime.Date constructs"
       __slots__ = ['_date']
       def __new__(cls, date='', month=0, day=0):
           nulldate = object.__new__(cls)
           nulldate._date = ""
                .
                .
                .
          return nulldate
       def __getattr__(self, name):
           if self:
               attribute = self._date.__getattribute__(name)
               return attribute
           else:
               if callable(dt.date.__dict__[name]):
                   return int
               else:
                   return 0
       def __nonzero__(self):
           if self._date:
               return True
           return False
       @classmethod
       def fromordinal(cls, number):
           if number:
               return cls(dt.date.fromordinal(number))
           else:
               return cls()
   NullDate.max = NullDate(dt.date.max)
   NullDate.min = NullDate(dt.date.min)
</trimmed down class code>

How can I move those last two lines into the class definition so that:
   1) they are class attributes (not instance), and
   2) they are NullDate type objects?


It can't be done by any straightforward method I know of. I advise
you not to worry about it, and just define them afterwards, perhaps
with an apologetic comment saying you would have put them inside the
class definition if you could have.


If the out-of-scope issue bothers you that much, you could use some
metaclass hackery to run a method that defines the class attributes
after the class is created, at the unrecommendable cost of confusing
most readers. Here is a very simple example:

def make_class_and_run_postcreate(name,bases,clsdict):
cls = type.__new__(type,name,bases,clsdict)
cls.__postcreate__()
return cls

class A(object):
__metaclass__ = make_class_and_run_postcreate
@classmethod
def __postcreate__(cls):
cls.internal_circular_class_ref = cls()


BTW, if you don't mind some criticism of your code, the code you
posted seems to be much too complex for the job you're describing.

First of all, do you even need to wrap the datetime.date class? With
Python's duck typing ability, you could have a separate NullDate class
to go alongside the datetime.date, and use a regular datetime.date
object when the date is present, and NullDate when it's absent. If
necessary you can subclass datetime.date to add any new methods it
would have to have. Use a factory function to return either NullDate
or a datetime.date depending on whether the dbf cell is empty.

class ValidDate(datetime.date):
def is_valid(self):
return True

class NullDate(object):
# implement any necessary methods of datetime.date interface here
def is_valid(self):
return False

def create_date_from_dbf_cell(dbf_cell):
if dbf_cell.empty():
return NullDate()
return ValidDate(dbf_cell.value)


If you do this, you don't have to muck around with __getattr__ or
__new__ or snooping to datetime.date's class dict anything like that.



Carl Banks
 
T

Terry Reedy

James said:
I resisted posting a similar question recently. After much
consideration, I realized that the inability to reference a class inside
its own definition must have been a deliberate design of the language.

The class *does not exist* to be referenced from inside its body until
after the body is executed and type(name, bases, namespace) is called.

class name(bases): body

is more or less equivalent to

name = type('name', bases, exec(body,{}))

except that the exec function (in 3.0) returns None instead of the
passed in namespace.

I think is would be possible to create and name-bind a blank class
first, but then there would need to be a mechanism delete the class if
the body execution failed. Because class definition can be nested, the
mechanism would need a stack of classes. In addition, this would be a
change in sequence from the usual assignment statement.

tjr
 
C

Carl Banks

Ethan said:
Greetings List!
I'm writing a wrapper to the datetime.date module to support having no
date.  Its intended use is to hold a date value from a dbf file, which
can be empty.
The class is functional at this point, but there is one thing I would
like to change -- datetime.date.max and datetime.date.min are class
attributes of datetime.date, and hold datetime.date values.  At this
point I have to have two lines outside the actual class definition to do
the same thing, e.g.:
<trimmed down class code>
  class NullDate(object):
      "adds null capable DateTime.Date constructs"
      __slots__ = ['_date']
      def __new__(cls, date='', month=0, day=0):
          nulldate = object.__new__(cls)
          nulldate._date = ""
          .
        .
        .
        return nulldate
      def __getattr__(self, name):
          if self:
              attribute = self._date.__getattribute__(name)
              return attribute
          else:
              if callable(dt.date.__dict__[name]):
                  return int
              else:
                  return 0
      def __nonzero__(self):
          if self._date:
              return True
          return False
      @classmethod
      def fromordinal(cls, number):
          if number:
              return cls(dt.date.fromordinal(number))
          else:
              return cls()
  NullDate.max = NullDate(dt.date.max)
  NullDate.min = NullDate(dt.date.min)
</trimmed down class code>
How can I move those last two lines into the class definition so that:
  1) they are class attributes (not instance), and
  2) they are NullDate type objects?

I resisted posting a similar question recently. After much
consideration, I realized that the inability to reference a class inside
its own definition must have been a deliberate design of the language.

I think mostly it's just a logistical issue. It's reasonable for a
class's attributes to be objects of that class (the datetime.date
class is a perfect example of when it's useful). It would be
preferrable to assign such objects inside the class scope like all
other class attributes.

Problem is, the class doesn't exist until after all the statements in
the class scope have been evaluated.

So the short answer is you can't.

The way you have done it is best--its not a hack and is good style.

Yes, it's straightforward and readable, a perfectly good workaround
for a very minor style issue.


Carl Banks
 
E

Ethan Furman

Carl said:
Greetings List!

I'm writing a wrapper to the datetime.date module to support having no
date. Its intended use is to hold a date value from a dbf file, which
can be empty.

The class is functional at this point, but there is one thing I would
like to change -- datetime.date.max and datetime.date.min are class
attributes of datetime.date, and hold datetime.date values. At this
point I have to have two lines outside the actual class definition to do
the same thing, e.g.:

<trimmed down class code>
class NullDate(object):
"adds null capable DateTime.Date constructs"
__slots__ = ['_date']
def __new__(cls, date='', month=0, day=0):
nulldate = object.__new__(cls)
nulldate._date = ""
.
.
.
return nulldate
NullDate.max = NullDate(dt.date.max)
NullDate.min = NullDate(dt.date.min)
</trimmed down class code>

How can I move those last two lines into the class definition so that:
1) they are class attributes (not instance), and
2) they are NullDate type objects?



It can't be done by any straightforward method I know of. I advise
you not to worry about it, and just define them afterwards, perhaps
with an apologetic comment saying you would have put them inside the
class definition if you could have.


If the out-of-scope issue bothers you that much, you could use some
metaclass hackery to run a method that defines the class attributes
after the class is created, at the unrecommendable cost of confusing
most readers. Here is a very simple example:

def make_class_and_run_postcreate(name,bases,clsdict):
cls = type.__new__(type,name,bases,clsdict)
cls.__postcreate__()
return cls

class A(object):
__metaclass__ = make_class_and_run_postcreate
@classmethod
def __postcreate__(cls):
cls.internal_circular_class_ref = cls()

Well, since "Practicality beats purity" ;) , I'll stick with having the
two assignment statements just outside the class. Not to mention my
metaclass skills are nonexistent at this point.
BTW, if you don't mind some criticism of your code, the code you
posted seems to be much too complex for the job you're describing.

Critiques always welcome.
First of all, do you even need to wrap the datetime.date class? With
Python's duck typing ability, you could have a separate NullDate class
to go alongside the datetime.date, and use a regular datetime.date
object when the date is present, and NullDate when it's absent. If
necessary you can subclass datetime.date to add any new methods it
would have to have. Use a factory function to return either NullDate
or a datetime.date depending on whether the dbf cell is empty.

class ValidDate(datetime.date):
def is_valid(self):
return True

class NullDate(object):
# implement any necessary methods of datetime.date interface here
def is_valid(self):
return False

def create_date_from_dbf_cell(dbf_cell):
if dbf_cell.empty():
return NullDate()
return ValidDate(dbf_cell.value)


If you do this, you don't have to muck around with __getattr__ or
__new__ or snooping to datetime.date's class dict anything like that.


Carl Banks

Good question. My goal with NullDate is to have a date object that I
can treat the same regardless of whether or not it actually holds a
date. NullDates with no value should sort before any NullDates with a
value, should be comparable to dates as well as NullDates, and should
support all the same methods. In other words, I don't want to have to
worry about whether my date object has an actual date under most
circumstances (printing, using as dictionary keys, comparing, etc.).

Does my design make more sense given these expanded requirements, or
could it still be done simpler? For that matter, do my requirements
make sense?

~Ethan~
 
C

Carl Banks

Good question.  My goal with NullDate is to have a date object that I
can treat the same regardless of whether or not it actually holds a
date.  NullDates with no value should sort before any NullDates with a
value, should be comparable to dates as well as NullDates, and should
support all the same methods.  In other words, I don't want to have to
worry about whether my date object has an actual date under most
circumstances (printing, using as dictionary keys, comparing, etc.).

Does my design make more sense given these expanded requirements, or
could it still be done simpler?  For that matter, do my requirements
make sense?


Your requirements make sense, but I think your approach is overkill.

In particular, I think you are unnecessarily constraining yourself by
forcing the dates and non-dates to be of the same data type. There's
usually no reason for that. Python's duck typing allows you to write
code that supports multiple types, as long as each type provides the
same methods and operations and such.

For instance, say you have a function like this that works for
ordinary datetime.date objects:

def print_date(date):
print date.strftime()

Now you want to be able to pass it a particular object that indicates
no date was specified. Just define the class, like so:

class NullDate(object):
def strftime(self):
print "<no date specified>"

Now you could pass either a regular datetime.date object, or a
NullDate object, like so:

print_date(datetime.date(1976,10,6))
print_date(NullDate())

So, to (almost) get what you want, you need to just define a NullDate
class that implements all the methods of datetime.date. Then the only
thing you have to do is, when you're extracting the date from your dbf
file, instead of always creating a datetime.date, create a
datetime.date object when the date is specified, and a NullDate object
when it's not. You could write a factory function to do it, for
instance:

def create_date_or_not(dbf_cell):
if dbf_cell.contents:
return datetime.date.fromordinal(dbf_cell.contents)
return NullDate()

Now, you did throw one little curveball into the requirements above:
that NullDate needs to be comparable with datetime.date objects. I'd
handle this by subclassing datetime.date to allow comparisons with
NullDates.

class ValidDate(datetime.date):
def __eq__(self,other):
if isinstance(other,NullDate):
return False
return super(ValidDate,self).__eq__(other)
# and so on

class NullDate(object):
def __eq__(self,other):
if isinstance(other,NullDate):
return True
return False
# note: in Python 3.0 you would want to throw exceptions
# for unexpected types

Then use ValidDate instead of datetime.date when the date is
specified.


Carl Banks
 
R

rdmurray

Good question. My goal with NullDate is to have a date object that I can
treat the same regardless of whether or not it actually holds a date.
NullDates with no value should sort before any NullDates with a value, should
be comparable to dates as well as NullDates, and should support all the same
methods. In other words, I don't want to have to worry about whether my date
object has an actual date under most circumstances (printing, using as
dictionary keys, comparing, etc.).

Does my design make more sense given these expanded requirements, or could it
still be done simpler? For that matter, do my requirements make sense?

What Carl is saying is that since python uses duck typing ("if it quacks
like a duck, it is a duck"), all you need to do is make your NullDate
object quack enough like a date to handle your use cases, and then you
can freely mix dates and NullDates _without having to care which one a
given object is_ (python won't care). (Until you do care, at which
point you can use isinstance to find out if it is a NullDate).

As far as I can see, your requirements as you've outlined them can be met
by mixing date objects and appropriately implemented NullDate objects.
And that way NullDates will _only_ represent null dates, thus making
its name more meaningful :).

To do this you just need to implement on NullDate those methods that
are going to give NullDate the behavior you need: the rich comparison
operators, __str__, __hash__, etc. Or it might be easier to subclass
date and override some of the methods.

Unless I'm misunderstanding your requirements, of course :)

--RDM
 
E

Ethan Furman

Thanks, Carl! Thanks, RDM!

Your examples and ideas are much appreciated.

Many thanks also to everyone else who responded.

~Ethan~
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top