473,738 Members | 6,332 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Why keep identity-based equality comparison?

Hello,

Guido has decided, in python-dev, that in Py3K the id-based order
comparisons will be dropped. This means that, for example, "{} < []"
will raise a TypeError instead of the current behaviour, which is
returning a value which is, really, id({}) < id([]).

He also said that default equality comparison will continue to be
identity-based. This means that x == y will never raise an exception,
as is the situation is now. Here's his reason:
Let me construct a hypothetical example: suppose we represent a car
and its parts as objects. Let's say each wheel is an object. Each
wheel is unique and we don't have equivalency classes for them.
However, it would be useful to construct sets of wheels (e.g. the set
of wheels currently on my car that have never had a flat tire). Python
sets use hashing just like dicts. The original hash() and __eq__
implementation would work exactly right for this purpose, and it seems
silly to have to add it to every object type that could possibly be
used as a set member (especially since this means that if a third
party library creates objects for you that don't implement __hash__
you'd have a hard time of adding it).


Now, I don't think it should be so. My reason is basically "explicit is
better than implicit" - I think that the == operator should be reserved
for value-based comparison, and raise an exception if the two objects
can't be meaningfully compared by value. If you want to check if two
objects are the same, you can always do "x is y". If you want to create
a set of objects based on their identity (that is, two different
objects with the same value are considered different elements), you
have two options:
1. Create another set type, which is identity-based - it doesn't care
about the hash value of objects, it just collects references to
objects. Instead of using set(), you would be able to use, say,
idset(), and everything would work as wanted.
2. Write a class like this:

class Ref(object):
def __init__(self, obj):
self._obj = obj
def __call__(self):
return self._obj
def __eq__(self, other):
return isinstance(othe r, Ref) and self._obj is other._obj
def __hash__(self):
return id(self._obj) ^ 0xBEEF

and use it like this:

st = set()
st.add(Ref(whee l1))
st.add(Ref(whee l2))
if Ref(wheel1) in st:
....
Those solutions allow the one who writes the class to define a
value-based comparison operator, and allow the user of the class to
explicitly state if he wants value-based behaviour or identity-based
behaviour.

A few more examples of why this explicit behaviour is good:

* Things like "Decimal(3. 0) == 3.0" will make more sense (raise an
exception which explains that decimals should not be compared to
floats, instead of returning False).
* You won't be able to use objects as keys, expecting them to be
compared by value, and causing a bug when they don't. I recently wrote
a sort-of OCR program, which contains a mapping from a numarray array
of bits to a character (the array is the pixel-image of the char).
Everything seemed to work, but the program didn't recognize any
characters. I discovered that the reason was that arrays are hashed
according to their identity, which is a thing I had to guess. If
default == operator were not defined, I would simply get a TypeError
immediately.
* It is more forward compatible - when it is discovered that two types
can sensibly be compared, the comparison can be defined, without
changing an existing behaviour which doesn't raise an exception.

My question is, what reasons are left for leaving the current default
equality operator for Py3K, not counting backwards-compatibility?
(assume that you have idset and iddict, so explicitness' cost is only
two characters, in Guido's example)

Thanks,
Noam

Jan 9 '06 #1
37 2808
sp*******@gmail .com writes:
My question is, what reasons are left for leaving the current default
equality operator for Py3K, not counting backwards-compatibility?
(assume that you have idset and iddict, so explicitness' cost is only
two characters, in Guido's example)


Yes. Searching for items in heterogenous containers. With your change
in place, the "in" operator becomes pretty much worthless on
containers of heterogenous objects. Ditto for container methods that
do searches for "equal" members. Whenever you compare two objects that
don't have the same type, you'll get an exception and terminate the
search. If the object your searching for would have been found
"later", you lose - you'll get the wrong answer.

You could fix this by patching all the appropriate methods. But then
how do you describe their behavior, without making some people expect
that it will raise an exception if they pass it incomparable types?

Also, every container type now has this split between identity and
equality has to be dealt with for *every* container class. If you want
identity comparisons on objects, you have to store them in an idlist
for the in operator and index methods to work properly.

I also think your basic idea is wrong. The expression "x == y" is
intuitively False if x and y aren't comparable. I'd say breaking that
is a bad thing. But if you don't break that, then having "x == y"
raise an exception for user classes seems wrong. The comparison should
be False unless they are the same object - which is exactly what
equality based on id gives us.

<mike
--
Mike Meyer <mw*@mired.or g> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Jan 10 '06 #2
Mike Meyer wrote:
My question is, what reasons are left for leaving the current default
equality operator for Py3K, not counting backwards-compatibility?
(assume that you have idset and iddict, so explicitness' cost is only
two characters, in Guido's example)


Yes. Searching for items in heterogenous containers. With your change
in place, the "in" operator becomes pretty much worthless on
containers of heterogenous objects. Ditto for container methods that
do searches for "equal" members. Whenever you compare two objects that
don't have the same type, you'll get an exception and terminate the
search. If the object your searching for would have been found
"later", you lose - you'll get the wrong answer.

You could fix this by patching all the appropriate methods. But then
how do you describe their behavior, without making some people expect
that it will raise an exception if they pass it incomparable types?

Also, every container type now has this split between identity and
equality has to be dealt with for *every* container class. If you want
identity comparisons on objects, you have to store them in an idlist
for the in operator and index methods to work properly.

I also think your basic idea is wrong. The expression "x == y" is
intuitively False if x and y aren't comparable. I'd say breaking that
is a bad thing. But if you don't break that, then having "x == y"
raise an exception for user classes seems wrong. The comparison should
be False unless they are the same object - which is exactly what
equality based on id gives us.


Seconded. All hell would break loose if Python didn't allow == for heterogenous
types, $DEITY only knows how many types I relied on it. Please don't let it go
in Py3k.
--
Giovanni Bajo
Jan 10 '06 #3
Op 2006-01-10, Mike Meyer schreef <mw*@mired.org> :
sp*******@gmail .com writes:
My question is, what reasons are left for leaving the current default
equality operator for Py3K, not counting backwards-compatibility?
(assume that you have idset and iddict, so explicitness' cost is only
two characters, in Guido's example)
Yes. Searching for items in heterogenous containers. With your change
in place, the "in" operator becomes pretty much worthless on
containers of heterogenous objects. Ditto for container methods that
do searches for "equal" members. Whenever you compare two objects that
don't have the same type, you'll get an exception and terminate the
search. If the object your searching for would have been found
"later", you lose - you'll get the wrong answer.


Maybe that is just a wrong implementation of the "in" operator.

One may agree on a protocol for the "in" operator to catch the
TypeError when it tests for equality and treating the raised
exception the same as the two elements not being equal.
You could fix this by patching all the appropriate methods. But then
how do you describe their behavior, without making some people expect
that it will raise an exception if they pass it incomparable types?
But that is already a problem. Remember the thread about the Enum
class which originally raised an exception when comparing values
from different Enums. This would already cause such a problem.

There is no way in python now to throw an exception when you
think comparing your object to some very different object
is just meaningless and using such an object in a container
that can be searched via the "in" operator.
Also, every container type now has this split between identity and
equality has to be dealt with for *every* container class. If you want
identity comparisons on objects, you have to store them in an idlist
for the in operator and index methods to work properly. I also think your basic idea is wrong. The expression "x == y" is
intuitively False if x and y aren't comparable.


I'm not that sure about the "intuitivel y". The author of the Enum
class didn't seem to find it that intuitive to just name one
counter example. IMO "x == y" turning up false when uncomparable
is just as intuitive as "x < y" turning up false when uncomparable
but a lot of people don't seem to agree with the latter. My impression
is that what is intuitive may vary wildly here.

But there are certainly circumstances that I would prefer 1 == (1,2)
to throw an exception instead of simply turning up False.

I would say some more thinking is needed in this area. Now we can
have weird circumstances where A == B and B == C but A != C.
I think such cases can be troublesome too for containers and the
"in" operator.

IMO some more thinking about this is needed before deciding this
would be a good idea or not.

--
Antoon Pardon
Jan 10 '06 #4
Antoon Pardon <ap*****@forel. vub.ac.be> writes:
Yes. Searching for items in heterogenous containers. With your change
in place, the "in" operator becomes pretty much worthless on
containers of heterogenous objects. Ditto for container methods that
do searches for "equal" members. Whenever you compare two objects that
don't have the same type, you'll get an exception and terminate the
search. If the object your searching for would have been found
"later", you lose - you'll get the wrong answer. Maybe that is just a wrong implementation of the "in" operator.


That's what I said just below:
You could fix this by patching all the appropriate methods. But then
how do you describe their behavior, without making some people expect
that it will raise an exception if they pass it incomparable types?

But that is already a problem. Remember the thread about the Enum
class which originally raised an exception when comparing values
from different Enums. This would already cause such a problem.


Yes, I remember. I also remember that it was eventually agreed that
that Enum behavior was broken.
There is no way in python now to throw an exception when you
think comparing your object to some very different object
is just meaningless and using such an object in a container
that can be searched via the "in" operator.
I claim that comparing for equality is *never* meaningless. Either two
objects are equal, or they aren't. It may be that they are of
different types - like the enum example above - in which case they
will never compare equal.

Note that this is different from an ordering. It's possible to have a
pair of objects - maybe even of the same type - that can't be ordered
in anyway. In this case, raising an exception when you try that
comarison makes sense.
Also, every container type now has this split between identity and
equality has to be dealt with for *every* container class. If you want
identity comparisons on objects, you have to store them in an idlist
for the in operator and index methods to work properly.
I also think your basic idea is wrong. The expression "x == y" is
intuitively False if x and y aren't comparable.

But there are certainly circumstances that I would prefer 1 == (1,2)
to throw an exception instead of simply turning up False.


So what are they?
I would say some more thinking is needed in this area. Now we can
have weird circumstances where A == B and B == C but A != C.
Nothing wierd about that at all. Anyone who's dealt with floats at all
should be used to it.
I think such cases can be troublesome too for containers and the
"in" operator.
I don't. Can you provide an example of where it is?
IMO some more thinking about this is needed before deciding this
would be a good idea or not.


Actually, what's need are examples of usages where breaking equality
into two (or more - most LISPs have three different definitions of
equality) different relations is usefull.

<mike
--
Mike Meyer <mw*@mired.or g> http://www.mired.org/home/mwm/
Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
Jan 10 '06 #5
On 9 Jan 2006 14:40:45 -0800, sp*******@gmail .com wrote:
Hello,

Guido has decided, in python-dev, that in Py3K the id-based order
comparisons will be dropped. This means that, for example, "{} < []"
will raise a TypeError instead of the current behaviour, which is
returning a value which is, really, id({}) < id([]).

He also said that default equality comparison will continue to be
identity-based. This means that x == y will never raise an exception,
as is the situation is now. Here's his reason:
Let me construct a hypothetical example: suppose we represent a car
and its parts as objects. Let's say each wheel is an object. Each
wheel is unique and we don't have equivalency classes for them.
However, it would be useful to construct sets of wheels (e.g. the set
of wheels currently on my car that have never had a flat tire). Python
sets use hashing just like dicts. The original hash() and __eq__
implementation would work exactly right for this purpose, and it seems
silly to have to add it to every object type that could possibly be
used as a set member (especially since this means that if a third
party library creates objects for you that don't implement __hash__
you'd have a hard time of adding it).


Now, I don't think it should be so. My reason is basically "explicit is
better than implicit" - I think that the == operator should be reserved
for value-based comparison, and raise an exception if the two objects
can't be meaningfully compared by value. If you want to check if two
objects are the same, you can always do "x is y". If you want to create
a set of objects based on their identity (that is, two different
objects with the same value are considered different elements), you
have two options:


I often want to be able to ask, is one object equal to another, where
they *might* be of the same type or notr.

If they aren't of the same type, then the answer to :

a == b

is obviously False. Otherwise I have to wrap the test in a
``try...except` ` block or compare type (and possibly then compare
value). Both of which are more verbose.

All the best,

Fuzzyman
http://www.voidspace.org.uk/python/index.shtml
Jan 10 '06 #6
Op 2006-01-10, Mike Meyer schreef <mw*@mired.org> :
Antoon Pardon <ap*****@forel. vub.ac.be> writes:
You could fix this by patching all the appropriate methods. But then
how do you describe their behavior, without making some people expect
that it will raise an exception if they pass it incomparable types?

But that is already a problem. Remember the thread about the Enum
class which originally raised an exception when comparing values
from different Enums. This would already cause such a problem.


Yes, I remember. I also remember that it was eventually agreed that
that Enum behavior was broken.


It is broken in the context of the current python behaviour.
In a different context with different behaviour of containers
such behaviour may very well be the most intuitive.

We are now talking about python3k and so such we should
be open to the possibility that what is broken in
current python may very well be desirable behaviour for
what python will evolve into.
There is no way in python now to throw an exception when you
think comparing your object to some very different object
is just meaningless and using such an object in a container
that can be searched via the "in" operator.


I claim that comparing for equality is *never* meaningless. Either two
objects are equal, or they aren't. It may be that they are of
different types - like the enum example above - in which case they
will never compare equal.

Note that this is different from an ordering. It's possible to have a
pair of objects - maybe even of the same type - that can't be ordered
in anyway. In this case, raising an exception when you try that
comarison makes sense.


IMO you have the choice between taking the mathematical route or
the practical route.

If you take the first choice you are right that comparing for
equality is never meaningless, but so is using the other comparisons.
If two objects are not comparable then we just have that a < b, a ==b
and a > b are all false.

Now you can take the practical option and decide that programmaticall y
it make no sense to compare a specific couple of values and throw an
exception in this case, but it doesn't matter much which test you are
conducting at that point.

Maybe python should adopt both approaches and introduce a new family
of comparators. Then one family will always succeed and the other
family can throw an exception.
Also, every container type now has this split between identity and
equality has to be dealt with for *every* container class. If you want
identity comparisons on objects, you have to store them in an idlist
for the in operator and index methods to work properly.
I also think your basic idea is wrong. The expression "x == y" is
intuitively False if x and y aren't comparable.

But there are certainly circumstances that I would prefer 1 == (1,2)
to throw an exception instead of simply turning up False.


So what are they?
I would say some more thinking is needed in this area. Now we can
have weird circumstances where A == B and B == C but A != C.


Nothing wierd about that at all. Anyone who's dealt with floats at all
should be used to it.


With floats that is entirely a problem of precision. When you are
working with discrete types such circumstances remain weird.
I think such cases can be troublesome too for containers and the
"in" operator.


I don't. Can you provide an example of where it is?


Well not with the "in" operator but with the index method of lists
which seems related enough.

If the "in" operator returns true one can use index to find out
an element in the container that compares equal. Now normally
it wouldn't make a difference whether you would make further
comparisons against the original object or against the object
in the list. But in this case it can make a difference and
it isn't obvious what one should do.
IMO some more thinking about this is needed before deciding this
would be a good idea or not.


Actually, what's need are examples of usages where breaking equality
into two (or more - most LISPs have three different definitions of
equality) different relations is usefull.


I think it is usefull because when I am looking for 1 in a list,
I'm not necessarily happy when I find 1.0 or decimal("1").

--
Antoon Pardon
Jan 10 '06 #7
Op 2006-01-10, Fuzzyman schreef <fuzzyman_no@sp am_voidspace.or g.uk>:
On 9 Jan 2006 14:40:45 -0800, sp*******@gmail .com wrote:
Hello,

Guido has decided, in python-dev, that in Py3K the id-based order
comparisons will be dropped. This means that, for example, "{} < []"
will raise a TypeError instead of the current behaviour, which is
returning a value which is, really, id({}) < id([]).

He also said that default equality comparison will continue to be
identity-based. This means that x == y will never raise an exception,
as is the situation is now. Here's his reason:
Let me construct a hypothetical example: suppose we represent a car
and its parts as objects. Let's say each wheel is an object. Each
wheel is unique and we don't have equivalency classes for them.
However, it would be useful to construct sets of wheels (e.g. the set
of wheels currently on my car that have never had a flat tire). Python
sets use hashing just like dicts. The original hash() and __eq__
implementation would work exactly right for this purpose, and it seems
silly to have to add it to every object type that could possibly be
used as a set member (especially since this means that if a third
party library creates objects for you that don't implement __hash__
you'd have a hard time of adding it).


Now, I don't think it should be so. My reason is basically "explicit is
better than implicit" - I think that the == operator should be reserved
for value-based comparison, and raise an exception if the two objects
can't be meaningfully compared by value. If you want to check if two
objects are the same, you can always do "x is y". If you want to create
a set of objects based on their identity (that is, two different
objects with the same value are considered different elements), you
have two options:


I often want to be able to ask, is one object equal to another, where
they *might* be of the same type or notr.

If they aren't of the same type, then the answer to :

a == b

is obviously False. Otherwise I have to wrap the test in a
``try...except` ` block or compare type (and possibly then compare
value). Both of which are more verbose.


If we are going to stick to one equal comparator then there will
always be cases that seem to be more verbose than needed. In the
case where you consider it an error if you are working with objects
of different classes you now have to expicitely test for unequal
types and raise an exception explicitly which is also more verbose.

IMO if they aren't of the same type then the answer to:

a < b

is just as obviously False as

a == b

Yet how things are proposed now, the first will throw an exception
and the latter will return False.

--
Antoon Pardon
Jan 10 '06 #8
On 10 Jan 2006 13:33:20 GMT, Antoon Pardon <ap*****@forel. vub.ac.be> wrote:
IMO if they aren't of the same type then the answer to:

a < b

is just as obviously False as

a == b

Yet how things are proposed now, the first will throw an exception
and the latter will return False.


I don't see the two comparisons as equivalent at all. If two things
are different, it does not follow that they can be ranked. If we have
two objects, such as a spark plug and a cam shaft, it is one thing to
say that the two are not the same object; it is quite another to say
that one is 'greater than' or 'less than' the other.

--

# p.d.
Jan 10 '06 #9
Op 2006-01-10, Peter Decker schreef <py******@gmail .com>:
On 10 Jan 2006 13:33:20 GMT, Antoon Pardon <ap*****@forel. vub.ac.be> wrote:
IMO if they aren't of the same type then the answer to:

a < b

is just as obviously False as

a == b

Yet how things are proposed now, the first will throw an exception
and the latter will return False.
I don't see the two comparisons as equivalent at all. If two things
are different, it does not follow that they can be ranked.


That a < b returns false doesn't imply that a and b can be ranked.
take sets. set([1,2]) and set([1,3)) can't be ranked but
set([1,2]) < set([1,3)) returns False just as set([1,2]) > set([1,3))
does.
If we have
two objects, such as a spark plug and a cam shaft, it is one thing to
say that the two are not the same object; it is quite another to say
that one is 'greater than' or 'less than' the other.


But we don't say the latter. What we do say is that one is 'not greater
than' and 'not lesser than' (and 'not equal to') the other.

--
Antoon Pardon
Jan 10 '06 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

2
4666
by: Edward | last post by:
SQL 7.0 I have a form in ASP.NET and I want to write the values to the SQL Server tables. The tables are Customer and Address tables. There will be an insert into the Customer table, and I need to use the Identity of this inserted record for the Foreign Key in the Address table insert.
5
8202
by: DBA | last post by:
I have an identity field on a table in SQL Server. The identity seed is 1 and the identity increment is 1. If I remove a record from this table, the identity sequence is broken. For example: Table contents prior to record delete: Fname(varchar), Lname (varchar), row_id (identity) -------------------------------------------------- Smith, Jane, 1 Smith, Tom, 2 Jones, Mark 3
5
9857
by: grzes | last post by:
MS SQL Server 2000. My case is: I have the table T with primary key calling __recid int without identity property. This table includes a lot of records (about 1000000). I need to convert __recid's data type to IDENTITY. As you know sql sentence: "alter table T alter column __recid int IDENTITY not null" does not work with not-empty tables. I use the SQL Enterprise Manager which can convert the field __recid into identity but I need...
2
1770
by: Devesh Aggarwal | last post by:
Hi, I have a backup and restore module in my project. The backup uses a typed dataset object (XSD) to get the data from database and creates a xml file as the backup file (using the WriteXml method of dataset). When doing the restore i have to overwrite the data from xml back to database. these are the steps that i follow. 1. get the data from database.
5
4010
by: Eugene | last post by:
I have a table EugeneTest(id_num, fname, minit, lname) where field "id_num" is type IDENTITY and has UNIQUE constraint Let's say 2 user execute this code at the same time: DECLARE @return integer use EugeneTest INSERT employees ( fname, minit, lname) VALUES
4
41596
by: brent.ryan | last post by:
How do I get the next int value for a column before I do an insert in MY SQL Server 2000? I'm currently using Oracle sequence and doing something like: select seq.nextval from dual; Then I do my insert into 3 different table all using the same uniqueID. I can't use the @@identity function because my application uses a connection pool and it's not garanteed that a connection won't be used
1
2270
by: Peter Morris [Droopy eyes software] | last post by:
Hi all In my login form (forms authentication) I check that the login is valid, retrieve an "Author" object, and keep track of the author's roles. string roles; if (author.IsAdministrator) roles = new string {"Admin", "Member"}; else roles = new string {"Member"};
3
4509
by: Dan | last post by:
I'm writing a record from an asp.net page to SQL Server. After the insert I'm selecting @@identity to return the ID of the record that I just wrote. It worked fine until I typed a semicolon into one of the string fields to be inserted. The string fields are inside single quotes in the INSERT command. With the semicolon in the string, the record is written correctly including the semicolon, but the identity is not returned. Without a...
3
2358
by: Rob | last post by:
Hi all, I have a bit of a complicated question, hope we have an SQL guru out there that can help us solve this killer problem. Due to the size of SQL Database we have (largest in the US), we try to pre-process large data files in IO until we are ready to insert directly into the database via BCP (quick, no constraints, no mess... well um that's the problem)
13
5795
by: PinkBishop | last post by:
I am using VS 2005 with a formview control trying to insert a record to my access db. The data is submitted to the main table no problem, but I need to carry the catID to the bridge table CatalogImage where imgID also needs to be placed. Below is my code behind to carry the catID using the Select @@Identity and insert imgID to the bridge table. No data is being entered into the bridge table.
0
8787
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
9473
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
9334
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
1
6750
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
4569
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
4824
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
1
3279
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
2
2744
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
3
2193
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.